写上一篇的RecyclerView实现侧滑删除的时候,还没考虑过封装RecyclerView.Adapter,因为google已经帮我们把ViewHolder给封装成了一种内部用的机制。后来发现现在做的一个小项目有大概6个列表,每个都是重复的事情,很麻烦。恰好想起之前看到的Hongyang大神的一篇ListView通用Adapter的封装,因此也就想封装一个RecyclerView的BaseAdapter了。
一、正常实现分析
首先来看一下正常的一个RecyclerView.Adapter如何实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener {
private Context mContext;
private List<Bean> mBeen;
private OnItemClickListener mListener;
public MyAdapter(Context context, List<Bean> been) {
this.mContext = context;
this.mBeen = been;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
view.setOnClickListener(this);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setTag(position);
Bean bean = mBeen.get(position);
holder.tv_item.setText(bean.getItem());
}
@Override
public int getItemCount() {
return mBeen.size();
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(this, v, (Integer) v.getTag());
}
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView tv_item;
public ViewHolder(View itemView) {
super(itemView);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClick(RecyclerView.Adapter adapter, View v, int position);
}
}
基本上就是这样了。
先继承RecyclerView.ViewHolder实现自己的ViewHolder,用于存储复用的子View中的控件引用,包括要对控件进行初始化的findViewById操作。然后在MyAdapter的构造函数中初始化上下文Context以及数据集合,之后重点是重写onCreateViewHolder(ViewGroup parent, int viewType)用于创建ViewHolder,重写onBindViewHolder(ViewHolder holder, int position)方法实现数据的绑定,以及getItemCount()实现获取数据总量。
另外,由于RecyclerView不支持setOnItemClickListener的操作,因此我们要自己添加点击事件。所以,我定义了一个OnItemClickListener接口,然后在onCreateViewHolder中对ItemView进行了点击事件的绑定操作,而点击事件的回调就是OnItemClickListener中的onItemClick(RecyclerView.Adapter adapter, View v, int position)方法。由于view是复用的,因此关键的地方就在于在onBindViewHolder(ViewHolder holder, int position)中给ItemView设置当前position的tag,然后发生点击事件时,则从View中取出对应的position确定位置,然后回调onItemClick方法即可。
二、抽象
以上面的代码为例,联想其他的列表,发现会变化的地方有4处:
- 数据Bean;
- onCreateViewHolder(ViewGroup parent, int viewType)里的布局文件;
- onBindViewHolder(ViewHolder holder, int position)里的数据绑定部分;
- 以及自定义的ViewHolder;
不变的地方有:
- 数据集合的集合以及上下文Context的赋值是可以不变的;
- onCreateViewHolder中除了布局文件不一致,也不会变化,当然ViewHolder我们下面再讨论;
- onBindViewHolder(ViewHolder holder, int position)中的setTag以及从集合中取出数据的操作是不变的;
- getItemCount就不用说了,如果集合不变,这里的写法就是死的;
- 当然还有OnItemClickListener的点击事件以及对应的点击回调处理也是不变的;
上述的数据集合我们可以默认是List集合,那么真正变化的其实是数据类型。
再来说一下ViewHolder,ViewHolder中看似我们每次都要针对不同布局来进行初始化操作,但是自定义ViewHolder中其实真正变化的是具体的子控件的引用持有和获取。那么,唯一的变化就在于控件不同,如果我们将控件放入到map中,以其id为key,实例为value,那么这里就相当简单了,也可以看做是不变的内容。那么,上面提到的onCreateViewHolder中VIewHolder的创建便也是固定的。
好,一个个来。
先抽象出ViewHolder:
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
RecyclerViewHolder(View itemView) {
super(itemView);
this.mViews = new SparseArray<>();
}
View getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
mViews.put(viewId, view);
}
return view;
}
}
很简单,我们采用Android中的SpareArray来缓存view的引用,该类避免了自动包装并且数据结构不依赖于别的实体对象。只需要在需要view的时候使用getView(int id)即可,该方法首先会从缓存中查找是否存在该view,如果存在则直接返回,不存在才会去findViewById并进行缓存。这样,就可以应对所有的子View的引用变化。
再来看一下RecyclerView.Adapter的抽象:
package com.example.davidchen.blogdemo.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
/**
* RecyclerView adapter基类
* <p>
* 封装了数据集合以及ItemView的点击事件回调,同时暴露 {@link #onBindData(RecyclerViewHolder, Object, int)}
* 用于数据与view绑定
*
* @param <T> A data bean class that will be used by the adapter.
* <p>
* Created by DavidChen on 2018/5/30.
*/
abstract class BaseRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder> implements View.OnClickListener {
private Context mContext;
private List<T> mData;
private int mLayoutId;
private OnItemClickListener mListener;
BaseRecyclerViewAdapter(Context context, List<T> data, int layoutId) {
this.mContext = context;
this.mData = data;
this.mLayoutId = layoutId;
}
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
view.setOnClickListener(this);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
holder.itemView.setTag(position);
T bean = mData.get(position);
onBindData(holder, bean, position);
}
@Override
public int getItemCount() {
return mData.size();
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(this, v, (Integer) v.getTag());
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mListener = onItemClickListener;
}
protected abstract void onBindData(RecyclerViewHolder holder, T bean, int position);
public interface OnItemClickListener {
void onItemClick(RecyclerView.Adapter adapter, View v, int position);
}
}
如上所示,
我们先用泛型来解决数据类型不一致的问题,并且在构造方法中就要求初始化布局文件的参数。
在onCreateViewHolder中直接通过构造函数中获得的布局来创建view,并且添加OnClickListener,最后再返回上面抽象出来的RecyclerViewHolder类。
在onBindViewHolder中设置Tag,并且获取到数据bean,并且通过抽象方法onBindData(RecyclerViewHolder, T, int)给以后扩展绑定数据提供便捷。
getItemCount中直接给出集合的大小。
同时在ItemView的OnClick事件中调用暴露给用户的OnItemClickListener接口,以便用户可以轻松的实现item的点击事件。
三、总结
综上我们可以得到一个扩展性良好的RecyclerView的通用Adapter,以后便不再需要繁琐的干着重复的操作了。
给出一个我写的具体的扩展类,看起来还是相当简洁的:
/**
* 清单列表adapter
* <p>
* Created by DavidChen on 2018/5/30.
*/
public class InventoryAdapter extends BaseRecyclerViewAdapter<Inventory> {
public InventoryAdapter(Context context, List<Inventory> data) {
super(context, data, R.layout.item_inventroy);
}
@Override
protected void onBindData(RecyclerViewHolder holder, Inventory bean, int position) {
((TextView) holder.getView(R.id.tv_item_desc)).setText(bean.getItemDesc());
String quantity = bean.getQuantity() + "箱";
((TextView) holder.getView(R.id.tv_quantity)).setText(quantity);
String detail = bean.getItemCode() + "/" + bean.getDate();
((TextView) holder.getView(R.id.tv_detail)).setText(detail);
String volume = bean.getVolume() + "方";
((TextView) holder.getView(R.id.tv_volume)).setText(volume);
}
}
好了,感兴趣可以到GitHub上查看下载 源码