注:该RecyclerView相关的封装参考自
1. 为RecyclerView打造通用Adapter 让RecyclerView更加好用
2. Android 优雅的为RecyclerView添加HeaderView和FooterView
封装通用的Adapter、ViewHolder
ViewHolder
为什么要封装ViewHodler?
因为RecyclerView的使用,必须配合ViewHolder,ViewHolder可以理解成存放RV子项的容器(ViewHolder的构造方法需要传递View类型的参数)。
而RecyclerView的内部类Recycler.ViewHolder为抽象类,也就意味着开发者需要自己实现ViewHolder类。
在第一行代码的RecyclerView使用示例来说,在Adapter里需要先定义一个内部ViewHolder类,写一个还好,但是因为RecyclerView在项目中使用的频率很高,如果每次使用都自己定义ViewHolder未免也太麻烦了,并且对于ViewHolder来说可以抽离出来公共的东西还是挺多的。
如何封装ViewHolder?
先思考,一个ViewHolder它需要干的事有哪些?
-
一个屏幕只能装下RecyclerView的若干个子项,内部会计算哪些position的子项能出现在屏幕上。RecyclerView的复用机制中讲过,ViewHolder是RV缓存复用的基本单位。当RV绘制的时候如果找不到对应position的VH缓存,就需要调用Adapter里的onCreateViewHolder方法新建ViewHolder实例。
-
在新建完实例或者能从缓存中找到对应position的ViewHolder后,需要为这个ViewHolder中的View实例配置值,如为其TextView传入字符串或为其Button绑定点击事件。这个绑定值就是在Adapter中的onBindViewHolder方法中执行。而给控件绑定方法,首先得通过findViewById获取该控件的实例对象,这个操作其实是挺耗时的。并且RecylerView这种会不停上下滑动的控件,每次对应position的ViewHolder展示在屏幕都要调用onBindViewHolder方法,如果每次都通过findViewById获取控件实例再配置值的话,就很影响性能。
通过上面两点可以得出,需要在ViewHolder类中设置控件实例变量,如:
但是我们想要封装ViewHolde是公共的,无法提前定义需要哪些控件,所以使用一个SparseArray来存储(类似Map,只不过key值只能是int)。通过这样的方式,每个控件只需要执行一次findViewById
//封装的ViewHolder
public class ViewHolder extends RecyclerView.ViewHolder{
//因为
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;
public ViewHolder(Context context, View itemView)
{
super(itemView);
mContext = context;
mConvertView = itemView;
mViews = new SparseArray<View>();
}
/**
* 通过viewId获取控件
*
* @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;
}
}
另外,Adapter中onCreateViewHolder方法的返回值为ViewHolder,我们可以在ViewHolder中暴露方法供外面调用来创建ViewHolder。
#VieHolder类 省略上面的代码了
public static ViewHolder createViewHolder(Context context, View itemView)
{
ViewHolder holder = new ViewHolder(context, itemView);
return holder;
}
public static ViewHolder createViewHolder(Context context,
ViewGroup parent, int layoutId)
{
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
ViewHolder holder = new ViewHolder(context, itemView);
return holder;
}
看到这里,你可能还会想想给子项赋上点击事件怎么办呢?在看完封装Adapter会揭晓。
Adapter
封装Adapter
封装是为了提取一些公共的逻辑,同样是由于RecyclerView.Adapter是抽象类必须子类去实现三个方法: onCreateViewHolder、 onBindViewHolder、 getItemCount。
除此之外,我们思考,通常使用一个Adpater需要定义的成员变量包括:
- 数据集合List
- 使用该Adapter的Activity(Context),比如执行startActivity没有context可不行。
除此之外为了方便使用还定义了mLayoutId(当前封装的为单类型的item)以及LayoutInfater。
通常我们在onBindViewHolder里给holder绑定数据,但是目前由于提取到了公共的封装类中,每个实际的Adapter想要绑定的东西都不一样。所以定义了一个抽象方法convert,让子类去实现具体的业务逻辑。
public abstract class MyBaseAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
protected Context mContext;
protected int mLayoutId;
protected List<T> mDatas;
protected LayoutInflater mInflater;
public MyBaseAdapter(Context context, int layoutId, List<T> datas)
{
mContext = context;
mInflater = LayoutInflater.from(context);
mLayoutId = layoutId;
mDatas = datas;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewHolder viewHolder = ViewHolder.createViewHolder(mContext, parent, mLayoutId);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// holder.updatePosition(position);
convert(holder, mDatas.get(position));
}
//我们这里直接抽象出去,让用户去操作。可以看到我们修改了下参数,用户可以拿到当前Item所需要的对象和viewHolder去操作。
public abstract void convert(ViewHolder holder, T t);
@Override
public int getItemCount() {
return mDatas.size();
}
}
装饰者模式封装RecyclerView
什么是装饰者模式这里就不提了。
为什么要使用装饰者模式封装
场景就是假设我们已经完成了RecyclerView的编写,Adapter这些已经写好了。但突然有个需求就是需要在RecyclerView列表中上加一个头部HeaderView。处理的方法就是要打开已经写好的Adapter加上viewType,加完后要修改getItemType、onCreateViewHolder、onBindViewHolder方法等,很多东西需要改变。
麻烦且容易出错,并且在RecyclerView上添加HeadView这种需求不少有,所以最好设计一个类,可以在不改变原有的Adapter的情况下添加header以及footer,这种需求就很契合装饰者模式
如何封装
原理:对于添加headerView或者footerView的思路。
其实HeaderView实际上也是Item的一种,只不过显示在顶部的位置,那么我们完全可以通过为其设置ItemType来完成。
- 由于需要包裹一个adapter实例对象,所以在它的构造函数中需要传入被包裹的adapter实例对象。
- 然后定义两个集合来存储header以及footer的view对象,这个集合类似于Map结构,key是int类型,value是view实例对象。暴露方法addHeaderView以及addFooterView来往集合里添加元素。
- 重写onCreateViewHolder,因为该方法有个参数int类型的参数叫viewType,根据这个参数从集合中获取到对应的view对象实例。
总体就是: 根据position来区分当前需要创建的ViewHolder属于header、footer还是其他的,如果是前两者就使用装饰类对象的方法来构造、如果是原本的就使用内部adapter对象的方法来构造。
/**
* 自己定义的Adapter、可以自由增加头部、尾部。配合自定义的ViewHolder使用
* @param
*/
public class HeaderAndFooterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;
private RecyclerView.Adapter mInnerAdapter;
//装饰者模式,需要传入需要包裹的对象
public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
{
mInnerAdapter = adapter;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
if (mHeaderViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;
} else if (mFootViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
return holder;
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (isHeaderViewPos(position))
{
return;
}
if (isFooterViewPos(position))
{
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
/*
总的Item数目:头部数目、尾部数目、原本的Item数目
*/
@Override
public int getItemCount()
{
return getHeadersCount() + getFootersCount() + getRealItemCount();
}
@Override
public int getItemViewType(int position)
{
if (isHeaderViewPos(position))
{
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position))
{
return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getHeadersCount());
}
/**
* 判断当前position对应的是否为头部
* @param position
* @return
*/
private boolean isHeaderViewPos(int position)
{
return position < getHeadersCount();
}
/**
* 判断当前position对应的是否为尾部
* @param position
* @return
*/
private boolean isFooterViewPos(int position)
{
return position >= getHeadersCount() + getRealItemCount();
}
private int getRealItemCount() {
return mInnerAdapter.getItemCount();
}
public void addHeaderView(View view)
{
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view)
{
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public int getHeadersCount()
{
return mHeaderViews.size();
}
public int getFootersCount()
{
return mFootViews.size();
}
}