ListView
和GirdView
的数据和界面展示是通过调用setData(ListAdapter apapter)
来实现。ListAdapter
的子类有BaseAdapter
,ArrayAdapter<T>
,CursorAdapter
,SimpleAdapter
等等。如何选择合适的子类:
ArrayAdapter<T>
其典型构造方法是:
ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)
适合于展示单行文本内容,需指定布局资源文件resource,且该布局必须包含一个TextView
,其id为textViewResourceId。显示的内容为T
的toString()
,无需重载getView
方法。网上有不少错误的用法是使用复杂的布局结构且重载getView
方法,将T
的各个属性设置到对应布局控件中,这样显然违反了这个类的设计初衷。
SimpleAdapter
其构造方法是:
SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
适用于显示含有若干Textview
和ImageView
的布局。其缺点有:
1.构建数据集很麻烦,通常我们获取的数据为List<T>
,T
为业务实体类。这里需要的是Map<String,?>
,需再转换一遍。
2.支持显示的view有限。TextView
,ImageView
和实现Checkable
接口的view。可通过setViewBinder
方法实现更多view绑定数据。但是自定义实现太多了,也违反了这个类的设计初衷。
CursorAdapter
其典型构造方法是:
CursorAdapter(Context context, Cursor c, boolean autoRequery)
该类的使用场景比较少,Cursor
游标,通常是从数据库或content provider获取数据时才出现。个人建议,底层获取数据集游标,可预先遍历数据集,转换为List
后再给上层UI使用。
BaseAdapter
该类比较灵活,是上面三个类的父类,也是我们最常使用的ListAdapter
子类,基本使用流程是:
1.定义一个实体类
class TestItem {
public String title;
public String conent;
}
2.继承BaseAdapter
public class TestAdapter extends BaseAdapter {
private List<TestItem> mData = new LinkedList<>();
private Activity mContext;
private LayoutInflater inflater;
public TestAdapter(Activity context) {
mContext = context;
inflater = LayoutInflater.from(context);
}
public void setData(List<TestItem> data, boolean reset) {
if (reset) {
mData.clear();
}
if (data != null) {
mData.addAll(data);
}
notifyDataSetChanged();
}
@Override
public int getCount() {
return mData.size();
}
@Override
public TestItem getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.refresh_list_item, null);
holder = new ViewHolder();
holder.titleTv = convertView.findViewById(R.id.title_tv);
holder.contentTv = convertView.findViewById(R.id.content_tv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
TestItem item = getItem(position);
holder.titleTv.setText(item.title);
holder.contentTv.setText(item.conent);
return convertView;
}
private static class ViewHolder {
public TextView titleTv;
public TextView contentTv;
}
}
在实现getView
方法中,我们需要判断convertView
是否已经可以复用,并且通过ViewHolder避免多次findViewById
,而上面三个子类已经实现了view复用。仔细观察TestAdapter
的实现,不难发现很多固定写法,能否进行一些抽象:
通过泛型代替具体实体类,
setData
,getCount
,getItem
,getItemId
可以固定实现抽取更加通用的ViewHolder,支持任意view
绑定数据由具体业务实现
-
布局文件由具体业务决定
public abstract class BaseAdapter<T> extends android.widget.BaseAdapter { protected Context mContext; protected List<T> mData = new LinkedList<>(); protected LayoutInflater mInflater; public BaseAdapter(Activity context) { mContext = context; mInflater = LayoutInflater.from(context); } @Override final public int getCount() { return mData.size(); } @Override final public T getItem(int position) { return mData.get(position); } /** 设置数据 */ final public void setData(List<T> data, boolean reset) { if (reset) { mData.clear(); } if (data != null) { mData.addAll(data); } notifyDataSetChanged(); } @Override final public long getItemId(int position) { return position; } @Override final public View getView(int position, View convertView, ViewGroup parent) { int itemViewType = getItemViewType(position); T item = getItem(position); ViewHolder holder; boolean reused = false; if (convertView == null) { int itemLayoutId = getItemLayoutId(itemViewType); convertView = mInflater.inflate(itemLayoutId, null); holder = new ViewHolder(convertView); convertView.setTag(holder); } else { reused = true; holder = (ViewHolder) convertView.getTag(); } handleItem(itemViewType, position, item, holder, reused); return convertView; } /** * 返回视图类型 * @param itemViewType 视图类型 * @return */ protected abstract int getItemLayoutId(int itemViewType); /** * 处理item,主要是填充数据 * @param itemViewType item对应的视图类型 * @param position 实体对应的索引位置 * @param item 具体实体 * @param holder */ protected abstract void handleItem(int itemViewType, int position, T item, ViewHolder holder, boolean reused); protected static class ViewHolder { private View mItemLayout; SparseArray<View> mViews = new SparseArray<View>(); public ViewHolder(View itemLayout) { mItemLayout = itemLayout; } public View gettemLayout() { return mItemLayout; } public <T extends View> T get(int viewId) { View childView = mViews.get(viewId); if (childView == null) { childView = mItemLayout.findViewById(viewId); mViews.put(viewId, childView); } return (T) childView; } public <T extends View> T get(int viewId, Class<T> viewClass) { View childView = mViews.get(viewId); if (childView == null) { childView = mItemLayout.findViewById(viewId); mViews.put(viewId, childView); } return (T) childView; } } }
现在TestAdapter的实现可以很简单:
private static class TestAdapter extends BaseAdapter<TestItem> {
public TestAdapter(Activity context) {
super(context);
}
@Override
protected int getItemLayoutId(int itemViewType) {
return R.layout.refresh_list_item;
}
@Override
protected void handleItem(int itemViewType, int position, TestItem item, ViewHolder holder, boolean reused) {
holder.get(R.id.title_tv, TextView.class).setText(item.title);
holder.get(R.id.content_tv, TextView.class).setText(item.conent);
}
}
getItemLayoutId
返回布局资源,当然可以根据itemViewType
返回不同的布局。handleItem
进行数据绑定。reused
表明该view是否复用,通过holder
的get(资源id)获取相应的控件。
结束
这一节介绍的ListAdapter是很基础的知识,大家应该非常熟悉,但是是否曾想抽象它,使我们的代码更加优雅!希望大家喜欢这个基础抽象类。