Android本身为ListView提供了几个方便的Adapter,比如ArrayAdapter、SimpleCurrentAdapter等等。但是为了实现更复杂的列表视图和控制,一般都要继承BaseAdapter来实现自己的Adapter。
我需要的ListView是如图一样的实现SD卡资源文件浏览列表,每个列表项由一个ImageView、TextView、CheckBox组成,并且要求当整个列表中有一个或一个以上的Checkbox被选中时,右上角的搜索按钮就显示出来,否则就隐藏,因此需要对每个列表项的CheckBox设置监听器。若使用Android提供的Adapter实现起来比较复杂,所以我选择继承BaseAdapter来实现自己的Adapter。
首先要知道的是ListView显示原理。ListView接收了Adapter之后,ListView的列表项数据是来源于接收的Adapter的。当ListView要展现出来的时候,ListView就会调用Adapter的getCount方法来获得一共需要绘制多少个列表项,之后就开始调用getView方法来获得每个列表项的View进行装载。也就是说ListView的列表项就是每次调用getView返回的View,每次调用getView获得的列表项View是什么样子,我们看到的这个列表项就是什么样子。
我继承BaseAdapter来实现自己的Adapter,至少需要重写基本的getView、getCount、getItem、getItemID四个方法。其中getCount和getView的功能如上所述,所以我要想实现对每个列表项多选框按钮的监听就需要在getView中返回View给ListView之前,对View中的多选框设置监听器。getView方法中带三个参数public View getView(int position,View convertView,ViewGroup parent),一般都是将convertView最为返回的View。
在这里,需要插播说明一下Android系统对ListView的实现小细节。Android构造ListView列表项的时候每次只会构造足够满足屏幕显示数量的列表项,一般都是10个左右。当ListView的列表项多于屏幕能够显示的列表项的时候,ListView就可以上下拉动,每次拉动显示后续列表项时就会再次调用getView方法来构造后续列表项的View。如果ListView是首次显示出来,那么getView的参数View convertView就是null空的;如果是拉动ListView调用的getView,那么这时getView的参数convertView就不再是null,而是随着拉动刚刚被拉走隐藏掉的列表项的View。这么做的好处是可以节省资源。
基于这个细节,如果重写getView方法时,要将参数convertView作为返回View,那么getView中就应该判断convertView是否为null。为空的话就需要使用Inflater构造出来,不为空的话就可以直接使用了。我的需求中需要对多选框进行监听,所以在返回convertView之前需要中convertView中获取多选框控件并设置监听器。
一开始,我以为这样就能够实现我的需求了,但是出来的结果却意想不到。当我点击一个多选框后,将列表往下拉,下面出来的没有选中的列表框也变成选中的状态。注意到我每次点击一个多选框后往下拉同步被选中的多选框的距离都是不变的,总是相隔11项。于是,回想getView中convertView参数的特点,当我往下拉的时候,ListView调用getView方法中的convertView就是回收来的因为拉动被隐藏的View。在我这个例子中,由于多选框是一种带有状态标示的控件,我的getView没有对其状态进行重新设置,所以就造成了这种奇怪现象。我的解决方法是在我实现的Adapter类中创建一个boolean数组用于保存对应列表项多选框的状态(getView中第一个参数position就是列表项ID,是根据数据来标识,不是根据列表项View来标识的,所以可以根据position来对列表项数据进行选中和非选中标识),每次调用getView都会在其中判断position位置上的boolean值来决定多选框的状态。
同样的,基于这个原理,使用其它带状态的控件也需要注意getView回收的问题。当然,也可以不使用convertView最为getView的返回结果,而在getView每次调用都重新构造一个View,或者Adapter类中构造一个与数据数量等长的View数组。不过这么做的话就比较消耗资源。
另外,BaseAdapter中的getItem和getItemId方法再构造ListView构造过程中并没有使用过,不过据说是在关于ListView的一些监听器中会调用到,所以继承BaseAdapter时最好也给这两个方法返回一个有意义的值。getItemId一般返回对应的position,getItem返回对应position的列表数据对象。