概述
相信做Android开发的写得最多的就是ListView,GridView的适配器吧,对于Adapter一般都继承BaseAdapter复写几个方法,getView里面使用ViewHolder模式,其实大部分的代码基本都是类似的。
网上也有一些自定义Adapter的封装,本篇博客的Adapter的可以适应于多种类型的layout,也就是实现对话效果,而且代码和类设计都比较简单。
效果图,其中图片和文字是两个不同的layout:
类设计图
从上面的类图我们可以看到,直接继承BaseAdapter是类MutiplyCommonAdapter,根据类名就知道,这个类可以实现多种不同的布局。
我们把只能实现一种布局看成是该类的特殊情况,继承并且新建了类CommonAdapter。这个类是我们最常使用到的类。
至于ViewHolder是一个辅助类,在常见的getView()方法中,我们总是设置一个ViewHolder来进行缓存,这里也是做了这个工作,并且提供了一些快捷设置的方法,例如根据id设置文本,背景等,减少了我们使用findViewById()的次数。
MutiplyCommonAdapter
public abstract class MutiplyCommonAdapter<T> extends BaseAdapter{
protected Context mContext;
protected List<T> mDatas;
private int[] layoutId;
public MutiplyCommonAdapter(Context context, List<T> datas, int... layoutId){
this.mContext = context;
this.mDatas = datas;
this.layoutId = layoutId;
}
@Override
public int getCount(){
return mDatas.size();
}
@Override
public T getItem(int position){
return mDatas.get(position);
}
@Override
public long getItemId(int position){
return position;
}
@Override
public int getViewTypeCount() {
return layoutId.length;
}
@Override
public abstract int getItemViewType(int position);
@Override
public View getView(int position, View convertView, ViewGroup parent){
ViewHolder holder = ViewHolder.get(mContext, convertView, parent,
layoutId[getItemViewType(position)], position);
convert(holder, getItem(position),getItemViewType(position));
return holder.getConvertView();
}
//type是getItemViewType(position)的返回值
public abstract void convert(ViewHolder holder, T t,int type);
}
观察上面的类,构造方法的最后传入了一个int[]类型的layout数组,根据layout数组的长度,我们就可以得到getViewTypeCount()的返回值。
另外getItemViewType(int position)需要使用者自己定义,因为实现根据需求各有不同,例如我们根据奇偶行实现不同的布局。
接下来最主要的一个方法就是getView()方法,里面使用了ViewHolder这个辅助类返回了ViewHolder,这样我们就不需要自己实现Viewholder了。
最后是convert()方法,这里是我们自己对控件进行操作的地方。
CommonAdapter
public abstract class CommonAdapter<T> extends MutiplyCommonAdapter<T>{
private CommonAdapter(Context context, List<T> datas, int... layoutId) {
super(context, datas, layoutId);
}
public CommonAdapter(Context context, List<T> datas, int layoutId) {
this(context, datas, new int[]{layoutId});
}
@Override
public int getItemViewType(int position) {
return 0;
}
public abstract void convert(ViewHolder holder, T t);
@Override
public void convert(ViewHolder holder, T t,int type){
convert(holder,t);
}
}
CommonAdapter目的是提供单一布局的Adapter。
显然getItemViewType(int position)返回0;
convert(ViewHolder holder, T t)不需要再传入layout类型了,因为只有一种类型。
ViewHolder
public class ViewHolder {
/** 控件引用缓存 */
private final SparseArray<View> mViews;
private final Context mContext;
private int mPosition;
private View mConvertView;
public ViewHolder(Context context, ViewGroup parent, int layoutId,int position){
mContext = context;
mPosition = position;
mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
mConvertView.setTag(this);
}
public static ViewHolder get(Context context, View convertView,
ViewGroup parent, int layoutId, int position){
if (convertView == null)
return new ViewHolder(context, parent, layoutId, position);
ViewHolder holder = (ViewHolder) convertView.getTag();
holder.mPosition = position;
return holder;
}
/**
* 根据id获取控件
* @param viewId
* @return
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(int viewId){
View view = mViews.get(viewId);
if (view == null){
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public ViewHolder setText(int viewId, String value) {
TextView view = getView(viewId);
view.setText(value);
return this;
}
...
}
首先构造函数中,使用LayoutInflater根据layoutId创建了布局View,并且将当期ViewHolder设置View的Tag。
然后是get()方法里面,判断View是否被缓存,如果是直接返回,否则根据Tag获取。
另外提供了一个getView()方法用于方便使用者用过id获取控件对象。其实这个类里面有很多这种工具方法,没有一一贴出来,典型的就是setText(int viewId, String value)方法。
简单使用
创建一个MyAdapter来根据position实现两种布局
public class MyAdapter extends MutiplyCommonAdapter<String>{
public MyAdapter(Context context, List<String> datas, int... layoutId) {
super(context, datas, layoutId);
}
//volley
public RequestQueue requestQueue = Volley.newRequestQueue(mContext);
public CombineCache mCombineCache = new CombineCache(mContext);
public ImageLoader mImageLoader = new ImageLoader(requestQueue, mCombineCache);
@Override
public int getItemViewType(int position) {
return position%2;//根据奇偶行设置不同的布局
}
@Override
public void convert(final ViewHolder holder, String url, int type) {
switch (type) {
case 0:
loadImg(holder,url);
break;
case 1:
loadText(holder,url);
break;
default:
break;
}
}
//图片布局
private void loadImg(final ViewHolder holder,String url){
mImageLoader.get(url, new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.i("cky", "error");
ImageView img = (ImageView)(holder.getView(R.id.img));
img.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_launcher));
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
ImageView img = (ImageView)(holder.getView(R.id.img));
img.setImageBitmap(response.getBitmap());
}
},400,400);
}
//文字布局
private void loadText(final ViewHolder holder,String url){
holder.setText(R.id.text,url);
}
}
在Activity里面我这样使用
final MyAdapter myAdapter = new MyAdapter(this,arr,R.layout.listview_cell,R.layout.listview_cell2);//arr是图片地址
listview.setAdapter(myAdapter);
写在最后
通过对Adapter的简单封装,我们大大减少了自定义Adapter过程中的麻烦。网上实现多布局Adapter的设计,都是以单个布局Adapter为基类,然后提供一个辅助类来实现多布局。
这样的思路显然不直观,单个布局Adapter是多个布局Adapter的一种特殊情况,那么他们的继承关系就很清楚了。
在ViewHolder中,我们并没有实现setImage()之类的为控件设置图片的方法,因为对于图片的加载,有许多不同的策略(例如Volley,UIL等),大家在实际使用的时候,可以根据项目添加这些方法。