2个关于Adapter库的源码分析(AdapterDelegate、BaseRecyclerViewHelper)

0x00 概述

核心思想:都是围绕如何解决viewType、视图、数据与视图的绑定来进行一系列的封装,不同的库有着自己不同的手段而已

Adapter能在数据变化时候,内部实现逻辑不会改变,仅在外部添加新功能即可,那么要求Adapter对数据层是解耦的,不能显示持有外部数据,核心要点要把握好 RecyclerView的3个核心API

  • getItemViewType : 根据position返回的一个int值,代表该position下的ViewHolder类型,通常在多种类型holder去复写该方法,默认返回0
  • onCreateViewHolder :创建对应ViewType的viewholder
  • onBinderViewHolder :绑定数据到对应的ViewHolder

如何抽出复用代码减少方便开发者使用。

基本要点

  1. 基本场景减少冗余代码
  2. 多种Type处理
  3. 下拉刷新、上拉加载,以及动画定制

扩展点

  1. item动画,item横向滑动等手势处理
  2. 多级折叠

本文主要是通过分析两个开源库完成对以上的学习体会,全文的目录如下

0x01 AdapterDelegates

设计思想

主要是使用自定义的Adapter去替代系统原生的Adapter,主要的APIonBindViewHolderonCreateViewHoldergetItemViewType方法都被劫持使用adpter内部的一个Manager类来实现,其实跟名字一样“Delagates-代理”,因此分析这个库的关键点就是要看代理后的对应api

类图

AbsDelegationAdapter :内部持有AdapterDelegatesManager去hookRecyclerViewAdapter的常用API的实现

AdapterDelegatesManager :实现RecyclerAdapter和Adapter之间的绑定,特别是viewType与对应的delegate之间映射的建立(核心类)

AdapterDelegate: 每种类型的抽象基类包括UI和业务逻辑(核心类),核心是onCreateViewHolder和onBindViewHolder

AbsListItemAdapterDelegate: 减少冗余代码编写,主要是通过泛型来避免类型转换(扩展类)

AbsFallbackAdapterDelegate: 默认实现类型,防止未知类型的奔溃(扩展类)

核心点分析

1.如何解决多种Type

这里要说明一下,3个核心api,AdapterDelegate负责创建viewholder和绑定数据到viewholder(核心是onCreateViewHolder和onBindViewHolder),每种AdapterDelegate的派发是由AdapterDelegatesManager的getItemViewType去获取;其中业务AdapterDelegate与对应的viewType建立映射是在调用AdapterDelegatesManager#addDelegate时建立。

先看demo中提供的使用方式找找感觉

public class ReptilesAdapter extends ListDelegationAdapter<List<DisplayableItem>> {

  public ReptilesAdapter(Activity activity, List<DisplayableItem> items) {

    // Delegates
    this.delegatesManager.addDelegate(new GeckoAdapterDelegate(activity)); //壁虎
    this.delegatesManager.addDelegate(new SnakeListItemAdapterDelegate(activity)); //蛇
    //setFallbackDelegate用于设置无对应type时默认显示item
    this.delegatesManager.setFallbackDelegate(new ReptilesFallbackDelegate(activity));

    setItems(items);
  }
}

可以看出,在上面提到的AdapterDelegatesManager

  • addDelegate : 构建SparseArrayCompat

0x02 BaseRecyclerViewAdapterHelper

https://github.com/CymChad/BaseRecyclerViewAdapterHelper

功能介绍

简介

BRVAH是一个强大的RecyclerAdapter框架,它能节约开发者大量的开发时间,集成了大部分列表常用需求解决方案(多种type、上拉加载、下拉刷新、item拖动)。该框架于2016年4月10号发布的第1个版本到现在已经一年多了,经历了800多次代码提交,140多次版本打包,修复了1000多个问题,获得了10000+ star,还做了一个专业的网站 http://www.recyclerview.org/

设计思想

主要是构造BaseQuickAdapterBaseViewHolder作为基准

BaseViewHolder里面预先内置了操作view的Id,因而引入了一些列set方法如 setTextsetImageResourcesetBackgroundColorsetTextColor

BaseQuickAdapter 主要对3个核心API做了封装,核心思想就是找到重复代码,抽取到基类,非重复部分代码用抽象方法代替,具体让子类实现

类图

这是BaseRecyclerViewAdapterHelper库的主要类图,可以看出

(1) BaseQuickAdapter实现了RecyclerView.Adapter中通用的抽象方法

(2) BaseQuickAdapter两个泛型

分析点1: 核心API的封装

BaseQuickAdapter.java 这个类中做了很多封装处理,我们按部就班来,这个类从Recycler.Adapter中派生而来,因而之前的三个核心API必不可少,

  • getItemViewType : 根据position返回的一个int值,代表该position下的ViewHolder类型
  • onCreateViewHolder :创建对应ViewType的viewholder
  • onBinderViewHolder :绑定数据到对应的ViewHolder

看看这个核心Adapter对这三个API做了什么事情,之后的工作也是从这三个API展开

BaseQuickAdapter#getItemViewType

获取创建View的Type类型

@Override
public int getItemViewType(int position) {
    //处理Empty空,根据0,1,2位置细致区分
    if (getEmptyViewCount() == 1) {
       ……
    }
    int numHeaders = getHeaderLayoutCount();
    if (position < numHeaders) {
        return HEADER_VIEW;
    } else {
        int adjPosition = position - numHeaders;
        int adapterCount = mData.size();
        if (adjPosition < adapterCount) {
            return getDefItemViewType(adjPosition); //核心处理创建viewType
        } 
        ……
    }
}

进一步往下看 核心处理,创建viewType

protected int getDefItemViewType(int position) {
    if (mMultiTypeDelegate != null) {
        return mMultiTypeDelegate.getDefItemViewType(mData, position); //创建多Type的代理
    }
    return super.getItemViewType(position); //返回值为0,即普通单个type返回值都是0
}

从这里我们可以看出根据data的index值以及我们是否开启空视图之类的数据来决定在onCreateViewHolder中应该返回什么类型的viewHolder。

当既不是头部视图、尾部视图、空视图、加载中视图时就会调用getDefItemViewType,若不是多Type,都是viewType都是返回为0。

核心函数调用链 getItemViewType->getDefItemViewType

有了viewType下一步就是如何创建对应的viewHolder

BaseQuickAdapter#onCreateViewHolde

//核心在default里面,其他的几个case都是创建几个特殊视图,item点击监听做了封装
@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
    K baseViewHolder = null;
    this.mContext = parent.getContext();
    this.mLayoutInflater = LayoutInflater.from(mContext);
    switch (viewType) {
        case LOADING_VIEW:
            baseViewHolder = getLoadingView(parent);
            break;
        case HEADER_VIEW:
            baseViewHolder = createBaseViewHolder(mHeaderLayout);
            break;
        case EMPTY_VIEW:
            baseViewHolder = createBaseViewHolder(mEmptyLayout);
            break;
        case FOOTER_VIEW:
            baseViewHolder = createBaseViewHolder(mFooterLayout);
            break;
        default:
            //注意 此时viewType = 0
            baseViewHolder = onCreateDefViewHolder(parent, viewType); //创建内容Item的viewHolder
            bindViewClickListener(baseViewHolder); //封装监听,主要是click和longClick
    }
    baseViewHolder.setAdapter(this);//1. 获取点击的位置,这里的位置是指 去除headLayoutCount的之后的位置,需要Adapter 2. 处理点击监听ChildView的点击包括 click、longClick
    return baseViewHolder;
}

可以看到创建内容Item的viewHolder实际交由函数onCreateDefViewHolder,函数调用链如下

onCreateDefViewHolder->createBaseViewHolder

protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
    int layoutId = mLayoutResId; //构造器传入BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data)
    if (mMultiTypeDelegate != null) { //多种类型type时候使用
        layoutId = mMultiTypeDelegate.getLayoutId(viewType);
    }
    return createBaseViewHolder(parent, layoutId);
}

protected K createBaseViewHolder(ViewGroup parent, int layoutResId) {
    return createBaseViewHolder(getItemView(layoutResId, parent));
}

protected K createBaseViewHolder(View view) {
    Class temp = getClass();
    Class z = null;
    while (z == null && null != temp) {
        //检查泛型是否使用了adapter的泛型
        z = getInstancedGenericKClass(temp);
        temp = temp.getSuperclass();
    }
    K k;
    // 泛型擦除会导致z为null
    if (z == null) {
        //若没有使用则创建一个基本的返回
        k = (K) new BaseViewHolder(view);
    } else {
        //从泛型派生来的,则创建出派生出来实际的ViewHolder
        k = createGenericKInstance(z, view);
    }
    return k != null ? k : (K) new BaseViewHolder(view);
}

至此可以看到实际上交由反射+泛型去创建viewholder的实例,继续往下追踪一下,泛型+反射是如何创建的,感觉是个学习泛型+反射技术的一个好的范例代码,具体注释都在代码中。

@SuppressWarnings("unchecked")
private K createGenericKInstance(Class z, View view) {
    try {
        Constructor constructor;
        // inner and unstatic class
        if (z.isMemberClass() && !Modifier.isStatic(z.getModifiers())) {
            constructor = z.getDeclaredConstructor(getClass(), View.class);
            constructor.setAccessible(true);
            return (K) constructor.newInstance(this, view);
        } else {
            constructor = z.getDeclaredConstructor(View.class);
            constructor.setAccessible(true);
            return (K) constructor.newInstance(view);
        }
    } 
    ……
    return null;
}

private Class getInstancedGenericKClass(Class z) {
    Type type = z.getGenericSuperclass(); //得到这个类的泛型父类
    if (type instanceof ParameterizedType) { //之前得到的泛型父类如果实现了ParameterizedType接口,即支持泛型
        Type[] types = ((ParameterizedType) type).getActualTypeArguments();//如果支持泛型则返回表示此类型是类型参数的Type对象数组(获取参数化类型中的实际参数类型)
        for (Type temp : types) {
            if (temp instanceof Class) {
                Class tempClass = (Class) temp;
                //BaseViewHolder.class.是 tempClass的父类 或者二者相同
                if (BaseViewHolder.class.isAssignableFrom(tempClass)) {
                    //校验是BaseViewHodler
                    return tempClass;
                }
            }
        }
    }
    return null;
}

再次小结一下创建viewholder的过程:

创建viewHolder比较有想法,主要是使用泛型反射,核心在createBaseViewHolder中,创建对应的ViewHolder时候先是判断这个hodler是否是从BaseViewHodler派生而来,让后再提取出具体holder的类型,并且创建出来。

函数调用链 onCreateViewHolder->onCreateDefViewHolder->createGenericKInstance

现在viewHolder已经有了,下面就是绑定数据

BaseQuickAdapter#onBindViewHolder

    @Override
    public void onBindViewHolder(K holder, int position) {
        //Add up fetch logic, almost like load more, but simpler.
        autoUpFetch(position);
        //Do not move position, need to change before LoadMoreView binding
        autoLoadMore(position);
        int viewType = holder.getItemViewType();

        switch (viewType) {
            //viewType = 0 是我们正常使用的数据,参见onCreateViewHolder default情形
            case 0:
                convert(holder, getItem(position - getHeaderLayoutCount()));
                break;
            case LOADING_VIEW:
                mLoadMoreView.convert(holder);
                break;
                ……
            default:
                convert(holder, getItem(position - getHeaderLayoutCount()));
                break;
        }
    }

    //获取对应holder的数据
    public T getItem(@IntRange(from = 0) int position) {
        if (position < mData.size())
            return mData.get(position);
        else
            return null;
    }

    //对外提供重写的绑定数据API
    protected abstract void convert(K helper, T item);

函数调用链 onBindViewHolder -> convert

viewType = 0 就是属于普通在数据的viewholder,然后将holder和其绑定的数据(mData.get(position))传入到convert函数中,这样holder和数据都有了,可以让使用方去绑定使用。

  • getItemViewType -> getDefItemViewType
  • onCreateViewHolder -> onCreateDefViewHolder->createGenericKInstance
  • onBindViewHolder -> convert
分析点2:多布局MultiType的处理

先回忆下 核心API

  • getItemViewType

从上面的分析也能看出一二,在getViewType中调用getDefItemViewType,涉及到了mMultiTypeDelegate这一概念,重点在BaseMultiItemQuickAdapter,实现多Type,使用的Adapter不再是直接继承自BaseQuickAdapter类,而是继承自其子类BaseMultiItemQuickAdapter,而且数据源也要实现MultiItemEntity接口

public interface MultiItemEntity {
    int getItemType();
}

意图很简单,是的每一个itemd对应的数据源都自带自身的ViewType,下面看下BaseMultiItemQuickAdapter,

public abstract class BaseMultiItemQuickAdapter<T extends MultiItemEntity, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {
    ……
    public BaseMultiItemQuickAdapter(List<T> data) {
        super(data);
    }
    //复写了核心API,获取每个位置的数据的ViewType
    @Override
    protected int getDefItemViewType(int position) {
        Object item = mData.get(position);
        if (item instanceof MultiItemEntity) {
            return ((MultiItemEntity) item).getItemType();
        }
        return DEFAULT_VIEW_TYPE;
    }
    ……

    @Override
    protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
        //核心API 创建对应ViewHodlder
        return createBaseViewHolder(parent, getLayoutId(viewType));
    }
    ……

    //添加ViewType的布局文件到集合中
    protected void addItemType(int type, @LayoutRes int layoutResId) {
        if (layouts == null) {
            layouts = new SparseIntArray();
        }
        layouts.put(type, layoutResId);
    }
}

viewType的返回、viewHolder的渲染,最后convert交由用户去执行数据的绑定,使用的时候从BaseMultiItemQuickAdapter中派生而来。

分析点3: 上拉加载&预加载

BaseQuickAdapter.java

加载更多

思考:

  • 加载更多可定制
  • 一些列回调,如何回调?
  • 如何实现加载更多(UI更新、加载更多Item插入)?

加载更多布局可定制,注入即可,否则使用默认配置。LoadMoreView是抽象类,支持派生定制

首先得实现加载更多监听接口,然后将其注入到BaseQuickAdapter中

public interface RequestLoadMoreListener {

    void onLoadMoreRequested();

}

public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener, RecyclerView recyclerView) {
    openLoadMore(requestLoadMoreListener);
    if (getRecyclerView() == null) {
        setRecyclerView(recyclerView);
    }
}

private void openLoadMore(RequestLoadMoreListener requestLoadMoreListener) {
    this.mRequestLoadMoreListener = requestLoadMoreListener;
    mNextLoadEnable = true;
    mLoadMoreEnable = true;
    mLoading = false;
}

在框架层BaseQuickAdapter中调用函数链如下

onBindViewHolder-> autoLoadMore ——>(回调)mRequestLoadMoreListener.onLoadMoreRequested(),从而完成闭环,对应的源码如下

   @Override
    public void onBindViewHolder(K holder, int position) {
        //Add up fetch logic, almost like load more, but simpler.
        autoUpFetch(position);
        //Do not move position, need to change before LoadMoreView binding
        autoLoadMore(position);
        int viewType = holder.getItemViewType();
        ……
    }


   private void autoLoadMore(int position) {
        ……
        mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING);
        if (!mLoading) {
            mLoading = true;
            if (getRecyclerView() != null) {
                getRecyclerView().post(new Runnable() {
                    @Override
                    public void run() {
                        mRequestLoadMoreListener.onLoadMoreRequested();
                    }
                });
            } else {
                mRequestLoadMoreListener.onLoadMoreRequested();
            }
        }
    }

然后在业务调用实现这个回调的同时,根据加载情况,使用不同的 BaseQuickAdapter提供的不同功能函数,刷新UI更新数据

  • BaseAdapter.loadMoreEnd(boolean gone) // 显示 没有更多

  • BaseAdapter.loadMoreComplete() //下拉刷新请求成功 关闭加载中

  • BaseAdapter.loadMoreFail() //下拉刷新请求出错 加载失败

设置loadMoreView更新状态刷新UI

   public void loadMoreEnd(boolean gone) {
        if (getLoadMoreViewCount() == 0) {
            return;
        }
        mLoading = false;
        mNextLoadEnable = false;
        mLoadMoreView.setLoadMoreEndGone(gone);
        if (gone) {
            notifyItemRemoved(getLoadMoreViewPosition());
        } else {
            mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END);
            notifyItemChanged(getLoadMoreViewPosition());
        }
    }
    ……
   }

整体思路是有了,下面来看下具体的操作

核心是在LoadMoreView,主要是将3种状态全部写在xml文件中,根据status visible/gone 。

研究完了加载部分核心功能之后,之后那么这个加载更多这个Item是在何时插入到Item之中的呢?

在使用加载更多时候是有一个开关来控制是否使用加载更多,

public void setEnableLoadMore(boolean enable) {
    int oldLoadMoreCount = getLoadMoreViewCount();
    mLoadMoreEnable = enable;
    int newLoadMoreCount = getLoadMoreViewCount();

    if (oldLoadMoreCount == 1) {
        if (newLoadMoreCount == 0) {
            //移除加载更多
            notifyItemRemoved(getLoadMoreViewPosition());
        }
    } else {
        if (newLoadMoreCount == 1) {
            //需要加载更多功能
            mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT);
            notifyItemInserted(getLoadMoreViewPosition());
        }
    }
}
//获取加载更多的位置
public int getLoadMoreViewPosition() {
    return getHeaderLayoutCount() + mData.size() + getFooterLayoutCount();
}

可以看到玄机就这这个开关之中~

分析点4:下拉刷新

思考

  • 集成到库中还是支持下拉定活

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </android.support.v4.widget.SwipeRefreshLayout>

缺点:失去一些自定义动画的效果,不过也符合谷歌的规范

分析点5:多层级折叠

思考:

  • 如何折叠 ?
  • 折叠的层级 ?

其实可以利用RecyclerView.Adapter给我们提供的如下一些通知数据源更新的方法来实现我们的动态伸展and折叠功能。

  @see #notifyItemChanged(int)
  @see #notifyItemInserted(int)
  @see #notifyItemRemoved(int)
  @see #notifyItemRangeChanged(int, int)
  @see #notifyItemRangeInserted(int, int)
  @see #notifyItemRangeRemoved(int, int)

当要伸展时,我们动态将下一级item的数据添加在与adapter绑定的数据集合中,然后通知layoutManager更新数据源。当要收缩时,将下一级的item的数据源从与adapter绑定的数据集合中移除,然后再刷新。

思路:

  1. 数据bean应该有存储自己数据的字段
  2. 数据bean应该有存储下一级litem列表的集合类型的字段
  3. 数据bean应该有一个字段标识当前item的状态(伸缩或者展开)
  4. 初始化adapter时只渲染顶级item
  5. 支持伸缩:点击当前状态由展开->折叠(将次级list插入adapter绑定的data集合中,刷新数据);当前状态由 折叠->展开(将次级的list从与adapter绑定的data集合中移除,刷新数据)
  6. 插入或移除的位置根据点击的item确认,插入量与移除根据下一级item数量确定
  7. 插入移除过程可以使用动画效果

实现Exandable and Collapse效果我们仍然是使用BaseMultiItemQuickAdapter实现,因为其本质还是一个对type的adapter,然后我们需要先看两个相关类

  • IExapandable接口
  • AbstractExpandableItem抽象类实现了IExpandable接口: 对数据bean的再次封装,某个bean如果

    public interface IExpandable<T> {
        boolean isExpanded(); //当前的bean是否展开
        void setExpanded(boolean expanded); //更新bean的当前状态
        List<T> getSubItems(); //获取下一级数据集合
        int getLevel();//返回当前的item属于第几个层级,第一级
    }
    

使用的时候按照如下规则创建Item,需要展开效果的item继承自AbstractExpandableItem<下一级Item的泛型>,最后一级不需要制定下一级Item的泛型

    public class Level0Item extends AbstractExpandableItem<Level1Item> implements MultiItemEntity {
        public String title;
        public String subTitle;

        public Level0Item( String title, String subTitle) {
            this.subTitle = subTitle;
            this.title = title;
        }

        @Override
        public int getItemType() {
            return ExpandableItemAdapter.TYPE_LEVEL_0;
        }

        @Override
        public int getLevel() {
            return 0;
        }
    }

看一个构建数据的例子,帮助理解一下

private ArrayList<MultiItemEntity> generateData() {
    int lv0Count = 9;
    int lv1Count = 3;
    int personCount = 5;

    String[] nameList = {"Bob", "Andy", "Lily", "Brown", "Bruce"};
    Random random = new Random();

    ArrayList<MultiItemEntity> res = new ArrayList<>();
    for (int i = 0; i < lv0Count; i++) {
        Level0Item lv0 = new Level0Item("This is " + i + "th item in Level 0", "subtitle of " + i);
        for (int j = 0; j < lv1Count; j++) {
            Level1Item lv1 = new Level1Item("Level 1 item: " + j, "(no animation)");
            for (int k = 0; k < personCount; k++) {
                lv1.addSubItem(new Person(nameList[k], random.nextInt(40)));
            }
            lv0.addSubItem(lv1);
        }
        res.add(lv0);
    }
    return res;
}

数据准备好了,下面看下细节如何完成展开和折叠

首先在ExandableItemAdapter#convert方法中除了完成数据绑定之外还需要设置点击折叠逻辑

下面看下核心函数 BaseQuickAdapter#collapseBaseQuickAdapter#expand

其中涉及到递归部分collapse->recursiveCollapse,expand->recursiveExpand,这里只分析一下展开过程,折叠过程类似

//展开逻辑
public int expand(@IntRange(from = 0) int position, boolean animate, boolean shouldNotify) {
    position -= getHeaderLayoutCount();

    IExpandable expandable = getExpandableItem(position); //获取展开节点
    if (expandable == null) {
        return 0;
    }
    if (!hasSubItems(expandable)) {
        expandable.setExpanded(false);
        return 0;
    }
    int subItemCount = 0;
    if (!expandable.isExpanded()) {
        List list = expandable.getSubItems();
        mData.addAll(position + 1, list);
        subItemCount += recursiveExpand(position + 1, list);//获取需要展开的数量

        expandable.setExpanded(true); //
        subItemCount += list.size();
    }
    ……
    return subItemCount;
}
private int recursiveExpand(int position, @NonNull List list) {
    int count = 0;
    int pos = position + list.size() - 1;
    for (int i = list.size() - 1; i >= 0; i--, pos--) {
        if (list.get(i) instanceof IExpandable) {
            IExpandable item = (IExpandable) list.get(i);
            if (item.isExpanded() && hasSubItems(item)) {
                List subList = item.getSubItems(); //获取子item
                mData.addAll(pos + 1, subList); //更新数据源
                int subItemCount = recursiveExpand(pos + 1, subList); //递归展开
                count += subItemCount;
            }
        }
    }
    return count;//最终返回需要展开的数量

}

总结:折叠->展开:mData添加需展开的数据集,更新数据源;展开->折叠:mData移除需折叠的数据集,更新数据源。

分析点6:点击相关

思考:

  • 在哪里实现,holder?
  • item和其内部子控件的点击处理

这个点其实跟上拉加载分析思路一致,监听回调

除了普通的item点击、长按之外还有childItem的长按和点击,

引入了childItem概念,比如每个Item整个点击就是通常意义的ItemClick,而里面比如每个按钮或者内部组件的组件的点击称为ChildItem,

下面从代码层面分析一下

先看BaseQuickAdapter中的点击接口

//item点击
public interface OnItemClickListener {
    void onItemClick(BaseQuickAdapter adapter, View view, int position);
}
//item长按
public interface OnItemLongClickListener {
    boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position);
}

//ChildItem点击
public interface OnItemChildClickListener {
    void onItemChildClick(BaseQuickAdapter adapter, View view, int position);
}

//ChildItem长按
public interface OnItemChildLongClickListener {
    boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position);
}

按照老的套路就是业务方在需要的情况下实现这4个接口,然后在框架BaseQuickAdapter和BaseViewHolder中调用

在BaseQuickAdapter中创建对应类型的holder中onCreateViewHolder,创建普通业务holder时,创建完hodler然后立即处理该holder的点击事件

@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
    K baseViewHolder = null;
    this.mContext = parent.getContext();
    this.mLayoutInflater = LayoutInflater.from(mContext);
    switch (viewType) {
        ……
        default:
            baseViewHolder = onCreateDefViewHolder(parent, viewType);
            bindViewClickListener(baseViewHolder);
    }
    ……
}

private void bindViewClickListener(final BaseViewHolder baseViewHolder) {
    ……
    //从BaseQucikAdapter转移到BaseViewHodler中,连接起了ItemClick的桥梁
    if (getOnItemClickListener() != null) {
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getOnItemClickListener().onItemClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
            }
        });
    }
    //连接起了ItemLongClick的桥梁
    if (getOnItemLongClickListener() != null) {
        view.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return getOnItemLongClickListener().onItemLongClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
            }
        });
    }
}

恩,处理了item的点击和长按,没有处理ItemChild的点击事件,因为此时它也无法处理,因为不知道那个子控件需要响应点击,再继续往下面看,先看业务类中如何注入

@Override
protected void onCreate(Bundle savedInstanceState) {
    ……
    adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
            Log.d(TAG, "onItemClick: ");
            Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show();
        }
    });
    adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
            Log.d(TAG, "onItemLongClick: ");
            Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show();
            return true;
        }
    });
    adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
        @Override
        public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
            Log.d(TAG, "onItemChildClick: ");
            Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show();
        }
    });
    adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
        @Override
        public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
            Log.d(TAG, "onItemChildLongClick: ");
            Toast.makeText(ItemClickActivity.this, "onItemChildLongClick" + position, Toast.LENGTH_SHORT).show();
            return true;
        }
    });

注入了我们所需要的4种点击事件的具体响应回调,再看下在ItemClickAdapter#convert,绑定具体Id,去操作

@Override
protected void convert(final BaseViewHolder helper, final ClickEntity item) {
    switch (helper.getItemViewType()) {
        case ClickEntity.CLICK_ITEM_VIEW:
            helper.addOnClickListener(R.id.btn);
            break;
        case ClickEntity.CLICK_ITEM_CHILD_VIEW:
            helper.addOnClickListener(R.id.iv_num_reduce).addOnClickListener(R.id.iv_num_add)
                    .addOnLongClickListener(R.id.iv_num_reduce).addOnLongClickListener(R.id.iv_num_add);
            // set img data
            break;
        case ClickEntity.LONG_CLICK_ITEM_VIEW:
            helper.addOnLongClickListener(R.id.btn);
            break;
        case ClickEntity.LONG_CLICK_ITEM_CHILD_VIEW:
            helper.addOnLongClickListener(R.id.iv_num_reduce).addOnLongClickListener(R.id.iv_num_add)
                    .addOnClickListener(R.id.iv_num_reduce).addOnClickListener(R.id.iv_num_add);
            break;
        ……
    }
}

重点关注下BaseViewHodler#addOnClickListenerBaseViewHodler#addOnLongClickListener,这里以addOnLongClickListener为例

public BaseViewHolder addOnClickListener(@IdRes final int viewId) {
    childClickViewIds.add(viewId);
    final View view = getView(viewId);
    if (view != null) {
        if (!view.isClickable()) {
            view.setClickable(true);
        }
        //原来在这里偷偷摸摸的把ItemChild的click的框架层面的回调完成了
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (adapter.getOnItemChildClickListener() != null) {
                    adapter.getOnItemChildClickListener().onItemChildClick(adapter, v, getClickPosition());
                }
            }
        });
    }

    return this;
}

之前的createViewholder已经绑定了一个正常的回调,在binderViewHodler->convert阶段再次个View添加了ChildItem的回调操作。

可以看到在点击处理响应的整体思路

  • 需要设置点击响应的具体从操作,这个放到了业务层面Actvity
  • 绑定具体控件(ItemClickAdapter#convert)封装层面提供用户去完成item内部即itemChild层面的点击,框架层面仅仅完成item的点击和长按
  • 调用addxxxListener,在框架层面不仅仅是绑定该控件的点击,顺带绑定了childView的响应

0x03 总结

AdapterDelegate的复盘

自定义的Adapter来hook原来的Adapter的API,但是其背后的hook思想却是比较常用,比如

  • vlayout(alibaba开源 Tangram 的基础部分,应用到天猫淘宝页面动态化)深度Hook了recyclerView的布局计算、创建等大量系统层面核心API
  • 换肤 Hook了AssetManager加载资源的路径
  • 插件化 Hook了系统API,“欺上瞒下、偷梁换柱”

等等

BaseRecyclerViewHelper的复盘

  • SOLID原则应用
  • 良好的封装
  • 泛型和反射结合的运用
  • Loadmoreview、点击处理的、折叠展开处理的思路
一种优雅的方式来使用RecyclerView核心思想  想必大家都遇到过,在一个列表中显示不同样式的需求。在RecyclerView中可以通过ViewType进行区分,如果样式特别多的时候就会使得代码非常冗余,不利于开发及维护。那么有没有一种优雅的方法解决这个问题呢?  技术经理给你说,接下来的项目由你负责,明天下班前把排期同步出来。这时你应该怎么做?由于你是Android端的RD,你对Android的排期是比较了解的,但是iOS端、FE端、Server端的排期怎么办呢?聪明的你立即把任务派发下去了,给每个端的负责人说,明天中午之前把排期汇总给我。  没错,在多样式列表的设计中也可以采用这种策略,给RecyclerView设置的Adapter不做具体的处理,而是由它派发出去。实现方案  1. addDelegate 向Adapter中注册委托Adapter;  2. addDataList 设置数据;  3. layout 渲染布局,Adapter查找到对应的委托Adapter,由委托Adapter去做具体渲染。如何使用引入compile 'com.kevin:delegationadapter:1.0.2' // 扩展,使得databinding更简易,可以不引入 compile 'com.kevin:delegationadapter-extras:1.0.0'用法 1. 同一数据类型多种样式// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); // 设置Adapter delegationAdapter = new DelegationAdapter(); // 添加委托Adapter delegationAdapter.addDelegate(new OnePicAdapterDelegate()); delegationAdapter.addDelegate(new ThreePicAdapterDelegate()); delegationAdapter.addDelegate(new MorePicAdapterDelegate()); delegationAdapter.addDelegate(new VideoAdapterDelegate()); recyclerView.setAdapter(delegationAdapter);2. 不同数据类型多种样式// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); mDelegationAdapter.addDelegate(new StringAdapterDelegate()); mDelegationAdapter.addDelegate(new IntegerAdapterDelegate()); mDelegationAdapter.addDelegate(new FloatAdapterDelegate()); mDelegationAdapter.addDelegate(new DoubleAdapterDelegate()); // 添加委托Adapter mRecyclerView.setAdapter(mDelegationAdapter); // 设置数据 List<Object> dataList = new ArrayList<>(); dataList.add("今天天气怎么样?");  // 添加一个String类型数据 dataList.add("大晴天,有点热。");  // 添加一个String类型数据 dataList.add("温度多少度呢?");    // 添加一个String类型数据 dataList.add(29);                // 添加一个int类型数据 dataList.add("具体是多少?");      // 添加一个String类型数据 dataList.add(29.5F);             // 添加一个Float类型数据 dataList.add(29.527921364978D);  // 添加一个Double类型数据 mDelegationAdapter.setDataItems(dataList); 3. 同一数据多种类型// 设置LayoutManager mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); // 设置LayoutManager mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new ServiceInfoAdapterDelegate()); mDelegationAdapter.addDelegate(new BillItemAdapterDelegate()); mDelegationAdapter.addDelegate(new ChargeInfoAdapterDelegate()); mBinding.recyclerView.setAdapter(mDelegationAdapter); // 设置数据 String newsListStr = LocalFileUtils.getStringFormAsset(this, "bill.json"); Bill bill = new Gson().fromJson(newsListStr, Bill.class); List<Object> dataList = new ArrayList<>(); dataList.add(new ItemData(bill, ServiceInfoDelegateAdapter.TAG)); dataList.addAll(bill.details); dataList.add(new ItemData(bill, ChargeInfoDelegateAdapter.TAG)); mDelegationAdapter.setDataItems(dataList);4. 添加头部// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); mDelegationAdapter.addDelegate(new BannerAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter); // 添加头部 mDelegationAdapter.addHeaderItem("这是添加的添加的头部数据"); 5. 添加尾部// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); mDelegationAdapter.addDelegate(new BannerAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter); // 添加尾部 mDelegationAdapter.addFotterItem("这是添加的添加的尾部数据"); 6. 带兜底的委托Adapter// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); // 添加兜底的委托Adapter mDelegationAdapter.setFallbackDelegate(new FallbackAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter);THANKS TO 1. MultiItem 委托思想来源 2. AdapterDelegates 委托架子来源
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值