ListView详解之三
参考http://www.cnblogs.com/angeldevil/archive/2011/11/20/2255972.html
上上篇,我们使用了几次Adapter,如ArrayAdapter、SimpleAdapter;上篇,我们又见识了更多的Adapter,有点晕了,到底什么是Adapter?
一、Adapter的作用
Adapter是AdapterView视图与数据之间的桥梁,Adapter提供对数据的访问,也负责为每一项数据产生一个对应的View。其作用如下图所示:
看了这幅图,我想你大概明白什么是Adapter了,不过你肯定也有了新的疑问,不管你有没有,反正我是有过了。新疑问:这不是一直在讲ListView吗?那AdapterView是什么呢?
大概猜一下,它应该是所有要用到Adapter的View的父类吧。去开发文档里面一看,果然如此,看下图:
再点进去,看看listview:
这下更明白了吧!不过说实话,我现在更迷惑的是android类的继承关系了,要说搞通所有类的继承关系,一下两下也不现实,先看看下面的Adapter的继承关系吧!
二、Adapter的继承结构
要了解Adapter的继承和派生关系了,还好前面了解了这点东西,http://www.cnblogs.com/fww330666557/archive/2012/01/11/2319613.html,以作参考。
然后在去看看Adapter:
什么?Adapter是一个接口?我一直以为它是类。
什么?接口可以派生类?“接口!=函数或方法”吗?http://www.cnblogs.com/huihui-gohay/archive/2009/12/13/1623070.html(这篇文章说了接口的问题,我没仔细看,你可以去看看)。
再确认一下,widget是个包,没学过java,到底什么是包?我的理解,跟一个文件差不多吧!
还有,要分清楚Adapter和AdapterView。
三、各个类的作用
下面的内容怎么说呢,有点繁杂,如果你有耐心,你就细细看吧,不过最好是先了解个大概,我们这里的关键任务是理解Adapter各个类之间的继承关系。
Adapter
Adapter做为这个继承结构的最顶层的基接口,定义了Adapter要实现的基本方法:
public interface Adapter {
//注册一个Observer,当Adapter所表示的数据改变时会通知它,DataSetObserver是一个抽象类,定义了两个方法:onChanged与onInvalidated
void registerDataSetObserver(DataSetObserver observer);
//取消注册一个Observer
void unregisterDataSetObserver(DataSetObserver observer);
//所表示的数据的项数
int getCount();
//返回指定位置的数据项
Object getItem(int position);
//返回指定位置的数据项的ID
long getItemId(int position);
//表示所有数据项的ID是否是稳定的,在BaseAdapter中默认返回了false,假设是不稳定的,在CursorAdapter中返回了true,Cursor中的_ID是不变的
boolean hasStableIds();
//为每一个数据项产生相应的视图
View getView(int position, View convertView, ViewGroup parent);
//为了避免产生大量的View浪费内存,在Android中,AdapterView中的View是可回收的使用的。比如你有100项数据要显示,而你的屏幕一次只能显示10条数据,则
//只产生10个View,当往下拖动要显示第11个View时,会把第1个View的引用传递过去,更新里面的数据再显示,也就是说View可重用,只是更新视图中的数据用于显示新
//的一项,如果一个视图的视图类型是IGNORE_ITEM_VIEW_TYPE的话,则此视图不会被重用
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
//获得相应位置的这图类型
int getItemViewType(int position);
//getView可以返回的View的类型数量。(在HeaderViewListAdapter中可以包含Header和Footer,getView可以返回Header、Footer及Adapter
//中的视图,但其getViewTypeCount的实现只是调用了内部Adapter的的getViewTypeCount,忽略了Header、Footer中的View Type,不懂。
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
boolean isEmpty();
}
Adapter有两个子接口,ListAdapter(列表)与SpinnerAdapter(下拉列表),它们都只定义了少数方法。一般除WrapperListAdapter接口及其实现类只实现了ListAdapter外,都同时实现了这两个接口。
ListAdapter
//是否在ListAdapter中的所有项都enabled,即是否所有项都selectable和clickable
public boolean areAllItemsEnabled();
//指定位置的项是否是enabled的
boolean isEnabled(int position);
SpinnerAdapter
//产生相应位置下拉项的视图
public View getDropDownView(int position, View convertView, ViewGroup parent);
BaseAdapter
一个抽象类,Adapter的基础实现类,一般作为其他实现类的基类,同时实现ListAdapter与SpinnerAdapter,提供了一些方法的默认实现:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
//提供一些方法,当数据改变时调用注册的DataSetObserver的回调函数
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
//通知相关联的视图,相应的数据已经改变
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
//通过getView实现
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
}
ArrayAdapter<T>
A concrete BaseAdapter that is backed by an array of arbitrary objects. By default this class expects that the provided resource id references a single TextView. If you want to use a more complex layout, use the constructors that also takes a field id. That field id should reference a TextView in the larger layout resource.
However the TextView is referenced, it will be filled with the toString() of each object in the array. You can add lists or arrays of custom objects. Override the toString() method of your objects to determine what text will be displayed for the item in the list.
To use something other than TextViews for the array display, for instance, ImageViews, or to have some of data besides toString() results fill the views, override getView() to return the type of view you want.
默认ArrayAdapter产生的视图期望一个TextView,或者也可以指定一个Layout并指定其中一个类型为TextView的资源的ID,其底下的数据可以是任意类型,但显示的时候会调用其toString()方法,也就是说只能用TextView显示文字,如果想显示其他的数据,要重写getView()
ArrayAdapter有5个构造函数:
public ArrayAdapter(Context context, int textViewResourceId) {
init(context, textViewResourceId, 0, new ArrayList<T>());
}
public ArrayAdapter(Context context, int resource, int textViewResourceId) {
init(context, resource, textViewResourceId, new ArrayList<T>());
}
public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
init(context, textViewResourceId, 0, Arrays.asList(objects));
}
public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
init(context, resource, textViewResourceId, Arrays.asList(objects));
}
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
init(context, textViewResourceId, 0, objects);
}
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
init(context, resource, textViewResourceId, objects);
}
最多有4个参数,分别为当前Context,layout(可省),TextView的ID与数据(可为数组或List),在构造函数中都调用了一个叫init的函数
private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects;
mFieldId = textViewResourceId;
}
分别赋值给类中的私有域,mInflater为LayoutInflater,产生相应项的视图。
类中有两个域保存数据
private ArrayList<T> mOriginalValues;
private List<T> mObjects;
在ArrayAdapter中还定义了add,insert,remove,clear函数用于改变数据,并定义了一个布尔变量mNotifyChange用于表示用这些函数改变数据后是否通知视图(调用notifyDataSetChanged,调用这个函数时会把mNotifyChange置为true。
一些函数的实现:
public int getCount() {
return mObjects.size();
}
public T getItem(int position) {
return mObjects.get(position);
}
public int getPosition(T item) {
return mObjects.indexOf(item);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mDropDownResource);
}
可以看到getView和getDropDownView都通过调用createViewFromResourse来产生视图。
private View createViewFromResource(int position, View convertView, ViewGroup parent,
int resource) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.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;
}
在createViewFromResource中,首先判断convertView是否存在,若不存在则inflate一个,然后判断mFieldID是否为0,若为0则表示传递给ArrayAdapter的资源ID为一TextView,否则是传递了一Layout,mFieldID为此Layout中TextView的ID。然后通过getItem取得相应位置的数据项,判断是否是CharSequence的实例,如果是直接setText,否则调用其toString()函数,可以ArrayAdapter默认只能给TextVext设置字符串,若要使用其他视图,需要重载getView或getDropDownView,一般情况下会继承BaseAdapter自定义自己的Adapter。
public static ArrayAdapter<CharSequence> createFromResource(Context context,
int textArrayResId, int textViewResId) {
CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
}
读取资源文件中定义的字符数组作为数据生成ArrayAdapter,可以看到只能用TextView视图,而不可以指定一Layout再指定Layout中一个TextView的ID。
在ArrayAdapter中还定义了一个ArrayFilter,继承自Filter,用于过滤数据项(当ListView有焦点时,通过键盘输入字符来进行列表项的过滤),其过滤方法为传入一CharSequence,判断相应数据项是否以此CharSequence开头,若不是则用空格分割些数据项,判断分割后的各字符串是否以此CharSequence开头,若是则保留(若数据不是CharSequence则调用其toString())。如果传入的CharSequence为null或长度为0则不过滤。
CursorAdapter
用于显示Cursor中的数据。
在构造函数中可传递一参数autoRequery表示当cursor的数据改变时是否自动调用cursor的requery()以保持视图数据为最新的。
此类中重写了hasStableIds(),返回true。
public boolean hasStableIds() {
return true;
}
在CursorAdapter中,重写的getView及getDropDownView判断传入的convertView是否为
null
,若为
null
及相应地调用newView()或newDropDownView()来生成一个视图,而newDropDownView()只有一条语句
return
newView(context, cursor, parent);所以最后都是调用newView(),newView()为
abstract
的,需要由子类重写。
当通过newView()产生一个View之后,会调用 bindView(v, mContext, mCursor);将cursor中的数据绑定到newView()产生的View之中,此方法同样为
abstract
的。
CursorAdapter实现了接口CursorFilter.CursorFilterClient中的方法
//改变cursor指向的数据
public void changeCursor(Cursor cursor)
//将cursor转变为CharSequence,返回""或调用cursor.toString()
public CharSequence convertToString(Cursor cursor)
//过滤数据
public Cursor runQueryOnBackgroundThread(CharSequence constraint)
ResourceCursorAdapter
如类名所示,该类继承自CursorAdapter,通过XML产生Views,该类只是简单地重写了一些函数,通过LayoutInflater.inflate将XML转换为View
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mDropDownLayout, parent, false);
}
SimpleCursorAdapter
一个ResourceCursorAdapter的简单实现类,用于把cursor中相应的列映射为XML定义的视图中的TextView和ImageView。
该类中定义了一个接口
public static interface ViewBinder {
boolean setViewValue(View view, Cursor cursor, int columnIndex);
}
用于设置把cursor中的列映射为视图的方法。
在SimpleCursorAdapter中重写了bindView,控制cursor到视图的绑定,其定义如下
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
final int[] to = mTo;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, cursor, from[i]);
}
if (!bound) {
String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}
if (v instanceof TextView) {
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
setViewImage((ImageView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleCursorAdapter");
}
}
}
}
}
可以看到,首先检查类中的私有域mViewBinder是否为null(默认为null,可通过setViewBinder)设置,为不为null则通过binder.setViewValue(v, cursor, from[i]); 进行绑定,这个函数若返回true则绑定成功,若返回false则通过SimpleCursorAdapter的规则绑定,判断相应的View是否为TextView或ImageView,若是则绑定,否则抛出异常。
由些可以看到,我们可以自定义一个类实现SimpleCursorAdapter.ViewBinder,然后通过setViewBinder来改变bindView的结果。
SimpleAdapter
一个BaseAdapter的实现类,用于绑定数据到一个XML定义的视图中。数据类型为ArrayList<Map<String, ?>>。
SimpleAdapter也实现了Filter接口用于数据的过滤,过滤方法类似ArrayAdapter,只是其数据类型为Map<String,?>,要判断Map中的每一项,若任意一顶符合要求就保留。
SimpleAdapter也是通过bindView函数进行数据的绑定,同SimpleCursorAdapter一样,SimpleAdapter也定义了一个相同的内部接口ViewBinder,在bindView中,首先判断是否通过setViewBinder设置了ViewBinder,若设置了则调用其setViewValue进行数据绑定,如果没有设置其setViewValue返回了false,则进行下面的处理:依次判断View是否为Checkable,TextView,ImageView并进行相应的处理,可见默认情况下SimpleAdapter也是处理TextView与ImageView,当然可以setViewBinder。
WrapperListAdapter
继承自ListAdapder的接口,所以也是一个ListAdapter,同时里面嵌入了另一个ListAdapter,只定义了一个函数用于取得嵌入的ListAdapter
public ListAdapter getWrappedAdapter();
HeaderViewListAdapter
继承自WrapperListAdapter,当你使用的ListView有页首(Header Views)或页尾(Footer Views)时使用。此类被设计用来作为一个基类,一般不需要使用。
类中定义了两个ArrayList用于保存页首和页尾
ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
这两个域不为null,在构造函数中判断,如果给这两个域赋值的参数为null,则将他们赋于值 HeaderViewListAdapter.EMPTY_INFO_LIST,其定义为
static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
new ArrayList<ListView.FixedViewInfo>();
其中ListView.FixedViewInfo的定义为
public class FixedViewInfo {
public View view;ListAdapter#getItem(int)}.
public Object data;
public boolean isSelectable;
}
该类重定义了getCount,areAllItemsEnabled等函数,把mHeaderViewInfos,mFooterViewInfos同时包括在内。而hasStableIds,getItemViewType与getViewTypeCount只考虑了其内嵌的ListAdapter
本篇完结。