深入理解使用ListView时ArrayAdapter、SimpleAdapter、BaseAdapter的原理
在使用ListView的时候,我们传给setAdapter方法的Adapter通常是ArrayAdapter、SimpleAdapter、BaseAdapter,但是这几个Adapter内部究竟是什么样子如果我们不搞清楚的话,在使用的时候就会感觉有些混乱,概括的说这三个Adapter之间的差异主要是由他们各自的getView方法的差异造成的,接下来我们一起看一下这几个Adapter的getView的源码
1.ArrayAdapter的getView方法源码如下:
public View getView(int position, View convertView, ViewGroup parent) { return createViewFromResource(mInflater, position, convertView, parent, mResource); } private View createViewFromResource(LayoutInflater inflater, int position, View convertView, ViewGroup parent, int resource) { View view; TextView text; if (convertView == null) { view = inflater.inflate(resource, parent, false); } else { view = convertView; } try { if (mFieldId == 0) { // If no custom field is assigned, assume the whole resource is a TextView text = (TextView) view; } else { // Otherwise, find the TextView field within the layout text = (TextView) view.findViewById(mFieldId); } } catch (ClassCastException e) { Log.e("ArrayAdapter", "You must supply a resource ID for a TextView"); throw new IllegalStateException( "ArrayAdapter requires the resource ID to be a TextView", e); } T item = getItem(position); if (item instanceof CharSequence) { text.setText((CharSequence)item); } else { text.setText(item.toString()); } return view; }
可以看到ArrayAdapter的getView方法直接调用了createViewFromResource方法,在这个方法里面用到了一个成员变量mFieldId ,我们往上翻一下源码可以看到他的定义如下:
private int mFieldId = 0;
再接着翻源码可以看到mFieldId的值只在构造函数中修改:
public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, @NonNull List<T> objects) { mContext = context; mInflater = LayoutInflater.from(context); mResource = mDropDownResource = resource; mObjects = objects; mFieldId = textViewResourceId; }
此时我们可以明白mFieldId的值就是在构造ArrayAdapter时传入的textViewResourceId,也就是布局文件中TextView的android:id属性的值,弄明白mFieldId后,我们接着分析,可以看到接下来对mFieldId进行了判断,如果mFieldId的值是0,那么传入整个布局文件的根节点就是一个TextView,如果mFieldId的值不为0,就在传入的布局文件中查找android:id为mFieldId的TextView,之后用getItem方法获取TextView的文字内容,然后用text的setText方法设置标题,至此我们可以明白ArrayAdapter只能对TextView及TextView的子类进行定制,ListView的每一项可以仅仅是一个TextView,也可以是一个布局文件,但是这个布局文件里面必须且只能包含一个android:id属性为textViewResourceId的TextView(TextView的子类当然也可以,因为他的子类也属于TextView)。
2.接下来分析SimpleAdapter,他的getView方法源码如下:
public View getView(int position, View convertView, ViewGroup parent) { return createViewFromResource(mInflater, position, convertView, parent, mResource); } 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; }
可以看到,在他的getView方法里面依然是调用了createViewFromResource方法,只是createViewFromResource方法和ArrayAdapter的createViewFromResource不同,在SimpleAdapter的createViewFromResource方法里面又调用了bindView方法,我们看一下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 ? "<unknown type>" : 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"); } } } } }
这时我们发现终于找到关键代码了,bindView先是获取第一项的数据dataSet ,然后通过to.length获取ListView的每一个列表项里面有几个要填充的控件,接下来是一个for循环,判断列表项里面的每个要填充的控件是具体什么东东,是Checkable还是TextView,还是ImageView。至此我们明白了SimpleAdapter只能填充Checkable、TextView、ImageView三种控件,在ListView的每一个列表项里面可以包含1~N个上面的三种控件,可以只有一种,也可以有两种,也可以都有,我们也可以看出每一个列表项都只能是一样的,通过SimpleAdapter我们不能让某个列表项和其他列表项不一样,通过继承BaseAdapter来自己实现getView则可以让我们任意定制列表项,在getView里面我们可以根据position的值决定返回何种类型的view。