本文并不算完全的原创,因为曾经受到某个博客的启发,可是那地址早就不记得了。
相信大家都知道listview中的item需要缓存,这样避免消耗更少的资源,所以,打造通用的RecyclerView的Adapter必须先从ViewHolder下手,那item的控件如何缓存起来呢?然后就不用findbyid,可能一些人想到可以根据HashMap键值对,根据控件的id和控件来存起来,这个也是对的,但是Android更推荐我们使用SparseArray,好了,废话不多说,直接上代码。
private final SparseArray<View> mViews=new SparseArray<>() ; private View mConvertView; private Context context; public ViewHolder(Context context,ViewGroup parent,View itemView) { super(itemView); mConvertView=itemView; this.context=context; } /** * 通过控件的Id获取对于的控件,如果没有则加入views * @param viewId * @return */ 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 View getConvertView() { return mConvertView; } /** * 为TextView设置字符串 * * @param viewId * @param text * @return */ public ViewHolder setText(int viewId, String text) { TextView view = getView(viewId); view.setText(text); return this; } /** * 为ImageView设置图片 * * @param viewId * @param drawableId * @return */ public ViewHolder setImageResource(int viewId, int drawableId) { ImageView view = getView(viewId); view.setImageResource(drawableId); return this; } /** * 为ImageView设置图片 * * @param viewId * @param * @return */ public ViewHolder setImageBitmap(int viewId, Bitmap bm) { ImageView view = getView(viewId); view.setImageBitmap(bm); return this; } }代码没什么好讲的,相信大家都看得懂,最主要的核心就是这个代码
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; }如果view不存在,就findViewById,否则就直接从 SparseArray取出来,也就是说,在第一个item加载进来的时候是需要findViewById的,然后从第二个开始就是不需要了,那么问题就来了。假设你的数据源中,第一个item是需要设置图片的,第二个item是不需要设置图片,如果你没有做处理,你会发现,第二个item和第一个item都是同一张图片,或者第一个item设置属性GONE,你会发现以后的item中都是GONE的,为什么呢?就像前面说的,加载第一个item的时候是findViewById,第二个就是从SparseArray取出来的,也就是说第一个item和第二个item的控件是同一个对象,你一直操作同一个对象,自然属性也是同步的。解决的办法就自然不需要我都说了。
最后是
public abstract class CommonAdapter<T> extends RecyclerView.Adapter<ViewHolder>{
private Context context;
protected List<T> listData;
private int layoutId;
private static final int TYPE_HEADER = 0;
private static final int TYPE_NORMAL = 1;
private static final int TYPE_FOOT=2;
public List<T> getListData() {
return listData;
}
private boolean isHaveFoot;
public CommonAdapter(Context context, List<T> listData, int layoutId) {
this.context = context;
this.listData = listData;
this.layoutId = layoutId;
}
/**
* 创建ViewHolder
* @param parent
* @param viewType
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(layoutId,parent,false);
return new ViewHolder(context,parent,view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
initDataView(holder, listData.get(position), position);;
}
/**
* 尾部是默认有的。
* @return
*/
@Override
public int getItemCount() {
listData.size();
} public abstract void initDataView(ViewHolder holder,T item,int position); public void reMoveAllData(){ if (listData!=null)listData.clear(); this.notifyDataSetChanged(); } /** * 增加数据 * @param list */ public void addData(List<T> list){ if (listData!=null){ listData.addAll(list); }else{ listData=list; } this.notifyDataSetChanged(); } //更新数据 public void updateData(List<T> list){ listData=list; } /** * 重写getItemType 用来标记是否为头部 * @param position * @return */ @Override public int getItemViewType(int position) { return TYPE_NORMAL; }}
细心的同学会发现为什么定义了
private static final int TYPE_HEADER = 0; private static final int TYPE_NORMAL = 1; private static final int TYPE_FOOT=2;这三个。这三个是区别头部和脚部的,如果你的需要头部刷新动画,或者脚步加载更多,这个还需要继续写,本次就先不讲这些了。上面的代码也还算简单,adapter先执行 onCreateViewHolder的方法,然后再绑定数据 onBindViewHolder,我们只需要写个抽象方法,让子类必须去实现数据源的绑定,这样就可以达到通用的目的了。