原文发表于vcmo博客
在Android项目开发中,经常会使用到的一个控件就是ListView,对与初学者来说,Adapter或许是一个新鲜的玩意儿。下面我们来一起学习一下ListView如何使用adapter。
为什么要使用Adapter?
adapter顾名思义是“适配器”的意思,它的作用就是将源数据与AdapterView绑定到一起,并且为每一个项创建视图。或许会问,为什么TextView、ImageView这类控件不需要Adapter呢。我的理解是,ListView是用于分批显示大量的数据,所以你总不会手动给它set进入对吧,计算机最擅长的就是重复。所以这种大量重复的的事交给计算机做就好了。
Adapter如何为ListView工作
使用过ListView我们知道,Adapter通过ListView的setAdapter()方法与之建立关系。在setAdapter()方法中将传入的adapter赋值给了ListView的全局变量mAdapter。mAdapter是定义在ListView的父类AbsListView中的一个ListAdapter对象,ListAdapter是一个接口继承自Adapter,BaseAdapter实现了ListAdapter接口。(不知道这样说的大家会不会很绕)
实现一个适配器
现在我们就开始一起写一个Adapter,继承自BaseAdapter,需要实现的方法如下
public class MyLVAdapter extends BaseAdapter {
private List datas; //数据的集合
public void setDatas(List datas) {
this.datas = datas;
notifyDataSetChanged();
}
public MyLVAdapter(List datas) { //在构造方法中传入数据并初始化。
this.datas = datas;
}
@Override
public int getCount() { //实现adapter中的抽象方法,获取数据的总数。
return datas.size();
}
@Override
public Object getItem(int position) { //实现adapter中的抽象方法,获取指定位置的数据对象。
return datas.get(position);
}
@Override
public long getItemId(int position) { //获取指定item的id,通常使用position作为该item的id。
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) { // 最重要的方法,创建每一个item的视图,并绑定数据。
Holder mHolder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_simple,null,false);
mHolder = new Holder(convertView);
convertView.setTag(mHolder);
}else {
mHolder = (Holder) convertView.getTag();
}
mHolder.setDatas(datas.get(position));
return convertView;
}
class Holder {
TextView name;
TextView message;
public Holder(View parentView) {
name = (TextView) parentView.findViewById(R.id.item_name);
message = (TextView) parentView.findViewById(R.id.item_message);
}
public void setDatas(ChatSimple chat){
name.setText(chat.name);
message.setText(chat.message);
}
}
}
ChatSimple是自定义的数据模型:
public class ChatSimple {
public String name;
public String message;
}
在上面的代码中,我们提到getView()方法,创建每一个item的视图并绑定数据。去ListView中看看它是如何被调用的。在ListView的父类AbsListView中可以找到adapter的getView()方法被调用的地方:
View obtainView(int position, boolean[] isScrap) {
···
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
···
}
首先会通过mRecycler获得指定位置的废弃View(ListView中itemView的循环利用)。调用adapter的getView得到绑定数据后的itemView。
再回到adapter中的
public View getView(int position, View convertView, ViewGroup parent) {
}
此时这里的传入的参数就一目了然
- position——指定item的位置
- convertView——循环利用的itemView对象,需要注意,在ListView刚开始构建的时候,这里传入的为null,因为还没有itemView可以被循环利用。所以需要根据convertView== null,判断是否需要填充新的itemView视图。
- parent——在AbsListView中调用时传入的是this,也就是这个Adapter关联的ListView对象。
上面我们的代码中还定义了一个Holder类型,并且将其绑定到convertView的Tag中。是为了提高效率,因为listView每个item的布局都是相同的,如果每次我们拿到循环的itemView,想要将数据对应的绑定上去,必须拿到相应的控件才能操作,又重新findViewById吗?不,不需要,只需要定义一个Holder,去做这件事,并且将控件保存,然后为每个ItemView绑定一个Holder。下次循环使用的时候,我们拿到了holder也就拿到了itemView所有的控件。
回看SimpleAdapter
SimpleAdapter是android系统提供的一个BaseAdapter的实现类,先看一些它的构造方法:
public SimpleAdapter(Context context, List extends Map> data,
@LayoutRes int resource, String[] from, @IdRes int[] to) {
mData = data;
mResource = mDropDownResource = resource;
mFrom = from;
mTo = to;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
了解一下这些参数:
- context:这个不必说,上下文。
- data:传入的数据集合,一个List,存储的类型是一个Map。
- resource:item的布局文件
- from:一个String类型的数组,这个数组就是上面data中每一项Map的key,会根据这个key拿到存在Map中真正的data。
- to:一个int类型的数组,也就是上面resource布局里面每一个控件的id值。据此拿到每一个控件的对象。
OK,上面说到Adapter中最重要的方法是getView,直奔getView方法:
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
调用了createViewFromResource方法:
private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
v = inflater.inflate(resource, parent, false);
} else {
v = convertView;
}
bindView(position, v);
return v;
}
这里面像我们前面分析的那样,根据convertView==null判断是否填充新的itemView,获得到itemView后调用了bindView()方法。继续往下看:
private void bindView(int position, View view) {
final Map dataSet = mData.get(position);
if (dataSet == null) {
return;
}
final ViewBinder binder = mViewBinder;
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
if (text == null) {
text = "";
}
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, data, text);
}
if (!bound) {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() +
" should be bound to a Boolean, not a " +
(data == null ? "" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
setViewImage((ImageView) v, (Integer) data);
} else {
setViewImage((ImageView) v, text);
}
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleAdapter");
}
}
}
}
}
代码不长,我们一步步来分析:
- 首先根据position去datas里拿到当前item的数据(Map)。
- 根据to[]获取到每一个控件,并从Map中拿到当前控件的数据。(可以看到,上面在to[]获得控件,并从from[]中得到该控件的数据,使用的是同一下标i,所以to[]与from[]数据必须是对应的)
- 判断view(根据to[]获取的控件视图)的类型,并调用相应的方法设置数据。
OK,到此我们已经学会了自己编写ListView的Adapter,并且分析了系统的实现类SimpleAdapter的实现过程。
欢迎分享交流
原创博文,转载请注明出处