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.3.5 适配器控件 - 图1

图 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);

//根据数据信息,进一步处理该事件

}

});