转载请注明出处:http://blog.csdn.net/demokui/article/details/54237496
写这篇文章的理由是这样子的,前天没事干自己封装了自认为是ListView和GridView的万能适配器,完了之后就在昨晚无意间浏览慕课网的时候看到了张鸿洋大神讲的万能适配器的系列课程,瞬间觉得自己写的就是垃圾中的战斗机,好了,我决定模拟大神的思路重新封装,文末会给出张鸿洋大神慕课视频地址以及CSDN相关地址。
哦,对了。我上一篇封装的“万能垃圾”在这 万能的ListView适配器
1、思路分析:
- ViewHolder是每个adapter中都会写内部类,所以想方设法抽取一个通用的ViewHolder。
- getView()通过通用的ViewHolder进行优化,实现灵活布局。
- 封装自定义BaseAdapter,与ViewHolder完美结合
2、ViewHolder的抽取。
首先我们通过传统的ViewHolder不难得出结论,我们在getView方法中使用内部类ViewHolder时,用了convertView是否为空作为入口。那么我们接下来抽取的公共ViewHolder类应该要有一个静态的入口方法:
/**
* 获取ViewHolder实例入口
*
* @param context
* @param convertView
* @param partent
* @param layoutId
* @param position
* @return
*/
public static ViewHolder getHolder(Context context, View convertView, ViewGroup partent, int layoutId, int position) {
if (convertView == null) { // 如果convertView等于空,实例化holder对象
return new ViewHolder(context, partent, layoutId, position);
} else {
ViewHolder holder = (ViewHolder) convertView.getTag();
holder.mPosition = position; //更新position
return holder;
}
}
我们看到当convertView为空的时候我们new了一个ViewHolder实例,那么ViewHolder具体做了什么呢?下面给出代码:
/**
* ViewHolder构造
*
* @param context 上下文,用来获得Infalter实例对象,加载Item布局
* @param partent 父级view
* @param layoutId Item布局资源ID
* @param position Item索引
*/
public ViewHolder(Context context, ViewGroup partent, int layoutId, int position) {
this.mPosition = position;
mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId,partent,false);
mConvertView.setTag(this);
}
这里解释一下SparseArray类:类似于Map集合,但此时key必须是int型,values必须是object型。如果项目中遇到这样的存储建议使用SparseArray效率要比map高很多(看鸿神视频的时候学到的,自己也看了源码,里面解释的很清楚,大家可以抽空看一下)。而我们这里主要用来存储viewID和View对象。记录查找过的控件,提高findViewById的效率。
- 通过viewID获取Item上的控件:
在通用的ViewHolder里面对外公布一个获得Item上的控件方法并将查找到的控件存储到SparseArray中。代码如下:
/**
* 通过viewID获取Item上的控件
* @param viewId 控件ID
* @param <T> view的泛型
* @return 返回的控件都是view的子类
*/
public <T extends View> T getItemView(int viewId){
View view = mViews.get(viewId);
//判断当我们的view集合里面没有存储该view,然后再去findviewByID。提高查询控件的效率
if(view == null){
view = mConvertView.findViewById(viewId);
mViews.put(viewId,view);
}
return (T) view;
}
- 最后我们需要返回一个convertView:
通常我们在getView方法中最后都要返回一个convertView,而我们通用的ViewHolder类中已经得到了这view,那么我们还需要对外公布一个获取当前convertView方法:
/**
* 对外公布获取convertView的方法
* @return
*/
public View getConvertView(){
return mConvertView;
}
- 到这里我们就已经将通用的ViewHolder类简单的封装完成了,那么如何使用呢?很简单,Follow me…
找到我们传统的Adapter类中的getView方法:
public View getView(int position, View convertView, ViewGroup parent){
// 首先调用ViewHolder的入口方法得到holder实例
ViewHolder holder = ViewHolder.getHolder(mContext,convertView,parent,mLayoutId,position);
// 调用getItemView方法得到控件
TextView tv = (TextView)holder.getItemView(R.id.tv);
// 设置数据
tv.setText("Hello world");
return holder.getConvertView();
}
看到这里是不是觉得我们的getView方法里面清爽了许多,可以直接获取holder对象,使用一行代码代替了我们传统的好多逻辑代码。那么我想告诉你,看到这里还不是高潮,高潮是还需酝酿,那么,继续往下看。。。
- LGBaseAdapter的抽取:
文章的标题是ListView和GridView的adapter一笔带过,那么完美的带过必须是跟adapter有关的。那么接下来我们就开始抽取通用的LGBaseAdapter:
首先我们知道常规的对adapter的封装无非就是对里面的getView方法的封装。那么这就很简单了,刚刚我们在上面其实已经给出getView方法了,我们可以看到其实就是做了这三步:
第一步:在getView方法里面我们首先是获取holder实例对象;
第二步:找控件、设置数据;
第三步:返回holder.getConvertView()。
然后细心的鸿神发现(超级崇拜鸿神)第一步和第三步是固定不变的,变化的只是其中的第二步,那么我们只需要在LGBaseAdapter类中抽象一个可以实现第二步的方法即可,而第二步中最关心的就是数据对象和VIewHolder实例,给出LGBaseAdapter的完整代码:
/**
* Created by DamonJiang on 2017/1/8.
*/
public abstract class LGBaseAdapter<T> extends BaseAdapter {
protected List<T> mList;
protected Context mContext;
private int mLayoutId;
public LGBaseAdapter(Context context, List<T> list,int layoutId) {
this.mList = list;
this.mContext = context;
this.mLayoutId = layoutId;
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public T getItem(int position) { // 这里将返回值类型设置成了泛型,由子类来确定
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
ViewHolder holder = ViewHolder.getHolder(mContext,convertView,parent,mLayoutId,position);
// 通过getItem获取数据对象。(因为我们并不知道得到子类的是什么类型的数据,所以我们将getItem 的返回值类型改为了T)
setViewData(holder,getItem(position));
return holder.getConvertView();
}
/**
* 这个方法交由子类去实现,设置Item上控件的数据
*/
public abstract void setViewData(ViewHolder holder,T t);
}
- 尝试初次使用万能的LGBaseAdapter:
/**
* Created by DamonJiang on 2017/1/8.
*/
public class MyAdapter extends LGBaseAdapter<Info> {
public MyAdapter(Context context, List<Info> list, int layoutId) {
super(context, list, layoutId);
}
@Override
public void setViewData(ViewHolder holder, Info info) {
((TextView) holder.getItemView(R.id.tv)).setText(info.getText());
}
}
现在再看我们的adapter是不是更是简单的不敢相信,不过请相信你的眼睛,以后我们的ListView、GridView的适配器就是这么简单。但是我又想说,这还并不是高潮,高潮是只有我们鸿神才能给的(哈哈哈。。。)怎么说呢?有时候我们Item上的控件会超级多,但无非就是一些TextView、EditText、ImageView等等一些常见控件。那么我们在 setViewData方法里面岂不是要写很多重复的没有技术含量的烦躁的代码?那么继续封装:
我们在通用的ViewHolder类中对外公布一些给常用控件设置数据的方法,例如:
- 给TextView设置数据
// 这里我们返回的是ViewHolder本身,为了能够方便链式编程
public ViewHolder setText(int viewId,String text){
TextView tv = getItemView(viewId);
tv.setText(text);
return this;
}
- 给ImageView设置图片
public ViewHolder setImageResource(int viewId,int resId){
ImageView iv = getItemView(viewId);
iv.setImageResource(resId);
return this;
}
- 然后我们的setViewData方法里面就变成了这样:
@Override
public void setViewData(ViewHolder holder, Info info) {
holder.setImageResource(R.id.id_icon, info.getmIcon());
holder.setText(R.id.id_title, info.getmTitle())
.setText(R.id.id_desc, info.getmDesc())
.setText(R.id.id_time, info.getmTime());
}
只要我们将常用的控件都这样添加到ViewHolder中去,那么我们的adpater就已经空前万能了(免得天外有天,人外有人,嘿嘿。。。)
到这里我们万能的适配器LGBaseAdapter就已经彻底的封装完成了,非常感谢张鸿洋相关视频教程,下面给出张鸿洋大神的相关adapter博文地址以及慕课网视频讲课地址:
谢谢!!!