封装通用的Adapter、ViewHolder以及装饰者模式封装RecyclerView

391 阅读6分钟

注:该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它需要干的事有哪些?

  1. 一个屏幕只能装下RecyclerView的若干个子项,内部会计算哪些position的子项能出现在屏幕上。RecyclerView的复用机制中讲过,ViewHolder是RV缓存复用的基本单位。当RV绘制的时候如果找不到对应position的VH缓存,就需要调用Adapter里的onCreateViewHolder方法新建ViewHolder实例。

  2. 在新建完实例或者能从缓存中找到对应position的ViewHolder后,需要为这个ViewHolder中的View实例配置值,如为其TextView传入字符串或为其Button绑定点击事件。这个绑定值就是在Adapter中的onBindViewHolder方法中执行。而给控件绑定方法,首先得通过findViewById获取该控件的实例对象,这个操作其实是挺耗时的。并且RecylerView这种会不停上下滑动的控件,每次对应position的ViewHolder展示在屏幕都要调用onBindViewHolder方法,如果每次都通过findViewById获取控件实例再配置值的话,就很影响性能。

通过上面两点可以得出,需要在ViewHolder类中设置控件实例变量,如:

image.png

但是我们想要封装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需要定义的成员变量包括:

  1. 数据集合List
  2. 使用该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来完成。

  1. 由于需要包裹一个adapter实例对象,所以在它的构造函数中需要传入被包裹的adapter实例对象。
  2. 然后定义两个集合来存储header以及footer的view对象,这个集合类似于Map结构,key是int类型,value是view实例对象。暴露方法addHeaderView以及addFooterView来往集合里添加元素。
  3. 重写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();
    }

}