7.3.5 适配器控件
适配器控件派生自android.widget.AdapterView,是一种特殊的容器控件,用于呈现一组相关联的数据信息。最典型的适配器控件是列表控件android.widget.ListView,它以垂直的列表形式来展示一组数据。其他典型的适配器控件还包括:网格控件GridView、下拉选择控件Spinner、相册控件Gallery,等等。
与一般的控件相比,适配器控件的结构更为复杂,它由三部分共同构成。
❑控件视图
控件视图由适配器控件对象及其中包含的若干个列表项(List Item)共同构成。每个列表项控件对象都是适配器控件的子控件对象,它们的控件样式往往是一致的,只是其中承载的数据有所不同。
❑数据源
适配器控件的目的,是为了呈现相关联的一组数据,这些数据都是由数据源提供的。数据源中可以包含各种格式的数据,比如:内存中的字符串数组、文件系统中的图片文件、数据库中的数据信息,等等。但无论如何,适合适配器控件展示的数据,都是由多行相关联的数据项汇集而成的,比如:字符串数组是由若干个字符串构成的,一组图片是由一张张图片组成的,而数据库中的数据则是由一行行数据项组成的。
❑适配器对象
控件视图和数据源是适配器控件的两个基本元素,而适配器对象则是结合两者的黏合剂。适配器对象均派生自android.widget.Adapter类,它的作用包括:构造列表项控件,并将数据项绑定到列表项控件中。
使用适配器控件,最重要的工作就是定义和实现适配器对象。Android为常见的数据源定义了一些适配器,比如:数组适配器ArrayAapter、数据库适配器CursorAdapter,等等。一个使用适配器对象的示例如下:
//定义数据
String[]data=new String[]{"早上好","中午好","晚上好"};
//定义适配器,并设置数据源
ArrayAdapter<String>adapter=new ArrayAdapter<String>(
this, data);
//将适配器与列表视图绑定
final ListView list=getListView();
list.setAdapter(adapter);
使用系统定义的适配器,只能满足简单的需求。在很多场景下,开发者需要根据需求自行定义适合列表展示的适配器对象。定制适配器通过派生Adapter及其子类来实现,示例如下:
public class CustomAdapter extends BaseAdapter{
//数据项的数据结构
public static class ListItem{
public int icon;
public String title;
};
private Context context;
private List<ListItem>items;//保存数据
public CustomAdapter(Context context,
List<ListItem>items){
this.context=context;
this.items=items;
}
@Override
public int getCount(){
return items.size();//列表项数量
}
@Override
public Object getItem(int position){
return items.get(position);//读取数据项
}
@Override
public long getItemId(int position){
return position;//数据项标识
}
@Override
public View getView(int position,
convertView, ViewGroup parent){
//构造列表项控件
View view=createListItemView();
//绑定数据
ListItem item=items.get(position);
((ImageView)view.findViewById(R.id.icon))
.setImageResource(item.icon);
((TextView)view.findViewById(R.id.title))
.setText(item.title);
return view;
}
private View createListItemView(){
LayoutInflater inflater=LayoutInflater.from(context);
return inflater.inflate(R.layout.list_item, this, null);
}
}
从上述示例可以看到,在适配器的定制过程中,开发者需要明确列表项的数量,并确定每个列表项对应的数据内容,同时需要为列表项构造列表控件对象,并将对应的列表项数据绑定到列表项控件上。
按照这样的方式构造适配器,当列表项很多时会导致子控件的数量急剧膨胀,耗费大量的内存资源,甚至导致应用崩溃。Android的适配器控件在设计时,充分考虑了处理大规模数据的场景,为开发者提供了解决策略。在Adapter.getView函数中,有一个输入参数convertView,用于缓存最近一个失去可视状态的列表项控件对象。如图7-13所示,适配器控件在屏幕区域内仅有一部分列表项可以被用户看到,我们将这样的列表项称为“可视列表项”。当用户滚动列表时,处于可视状态的列表项会变成不可视状态,而不可视的列表项则可能会变成可视状态。convertView便是用于缓存失去可视状态的列表项控件对象,通过Adapter.getView函数回传到开发者手中,开发者可以复用这个控件对象重新绑定即将可视的列表项数据,从而避免了构造新列表项控件的开销。
图 7-13 列表项的可视性
利用convertView构造列表项的示例代码如下:
public class CustomAdapter extends BaseAdapter{
…//省略重复代码
//缓存数据项控件对象
public static class ItemView{
public ImageView icon;
public TextView title;
};
@Override
public View getView(int position,
View convertView, ViewGroup parent){
//构造列表项控件
View view=null;
if(convertView!=null){
//复用控件
view=convertView;
}else{
//构造列表项控件,用ItemView进行缓存
view=createListItemView();
ItemView buffer=new ItemView();
buffer.icon=view.findViewById(R.id.icon);
buffer.title=view.findViewById(R.id.title);
view.setTag(buffer);
}
//绑定数据
ListItem item=items.get(position);
ItemView buffer=(ItemView)view.getTag();
buffer.icon.setImageResource(item.icon);
buffer.title.setText(itme.title);
return view;
}
}
复用convertView,可以大大减少列表项构造的时间和控件开销,但同时,也会增加数据项绑定的时间开销。因此,在使用convertView的时候,常常会对列表项控件的子控件进行缓存,通过View.setTag将此缓存放在对应的列表项控件当中,从而避免反复调用View.findViewById函数造成的额外开销。
通过列表项复用,可以有效地提高列表项的性能,节约内存开销,但同时它也增加了编程的复杂度,尤其是存在多种列表项样式时(不是每一个列表项控件都可以通过同一个样式进行设定),这种复杂度的增加也尤为明显。开发者需要根据列表项的数量和特征,选择合适的构造方法。
在适配器控件使用中,以列表项的形式展示数据只是其工作的一部分,另外它还需要支持列表项与用户的交互。开发者可以使用AdapterView.setOnItemClickListener等函数来监听用户与列表项的交互事件,比如:
ListView list=getListView();
list.setOnItemClickListener(new OnItemClickListener(){
@override
public void onItemClick(AdapterView<?>parent,
View view, int position, long id){
//获得对应位置的数据信息
Object data=parent.getListItemAtPosition(position);
//根据数据信息,进一步处理该事件
…
}
});