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

  • 项目地址 https://github.com/sockeqwe/AdapterDelegates

  • 解决问题: “Favor composition over inheritance“ for RecyclerView Adapters”
    “,解决了多种Type的问题

设计思想

主要是使用自定义的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) {// Delegatesthis.delegatesManager.addDelegate(new GeckoAdapterDelegate(activity)); //壁虎this.delegatesManager.addDelegate(new SnakeListItemAdapterDelegate(activity)); //蛇//setFallbackDelegate用于设置无对应type时默认显示itemthis.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 = 0baseViewHolder = onCreateDefViewHolder(parent, viewType); //创建内容Item的viewHolderbindViewClickListener(baseViewHolder); //封装监听,主要是click和longClick}baseViewHolder.setAdapter(this);//1. 获取点击的位置,这里的位置是指 去除headLayoutCount的之后的位置,需要Adapter 2. 处理点击监听ChildView的点击包括 click、longClickreturn 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为nullif (z == null) {//若没有使用则创建一个基本的返回k = (K) new BaseViewHolder(view);} else {//从泛型派生来的,则创建出派生出来实际的ViewHolderk = 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 classif (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)) {//校验是BaseViewHodlerreturn tempClass;}}}}return null;
}

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

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

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

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

BaseQuickAdapter#onBindViewHolder

    @Overridepublic 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 bindingautoLoadMore(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);elsereturn null;}//对外提供重写的绑定数据APIprotected 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@Overrideprotected int getDefItemViewType(int position) {Object item = mData.get(position);if (item instanceof MultiItemEntity) {return ((MultiItemEntity) item).getItemType();}return DEFAULT_VIEW_TYPE;}……@Overrideprotected K onCreateDefViewHolder(ViewGroup parent, int viewType) {//核心API 创建对应ViewHodlderreturn 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(),从而完成闭环,对应的源码如下

   @Overridepublic 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 bindingautoLoadMore(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() {@Overridepublic 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.SwipeRefreshLayoutandroid:id="@+id/swipeLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><android.support.v7.widget.RecyclerViewandroid: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;}@Overridepublic int getItemType() {return ExpandableItemAdapter.TYPE_LEVEL_0;}@Overridepublic 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(); //获取子itemmData.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() {@Overridepublic void onClick(View v) {getOnItemClickListener().onItemClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());}});}//连接起了ItemLongClick的桥梁if (getOnItemLongClickListener() != null) {view.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 databreak;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() {@Overridepublic 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、点击处理的、折叠展开处理的思路

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

  1. 第三季2:ORTP库的源码分析、RTP发送实验的源码分析

    以下内容源于朱有鹏课程,如有侵权,请告知删除. 一.ORTP库源码分析 1.ORTP库概览 (1)库提供一堆功能函数(本身没有main),都在src目录下 (2)库的使用给了案例(有main),在sr ...

  2. 在ADSP21489上使用FFT和IFFT库完整源码--分析窗为矩形窗

    时隔8年再次重新在21489上重新整理此功能,这次是完整的源码,重新把工程书写了搭建了一遍 再次编辑又有新的心得,学到了当初不曾了解全面的知识点 1.ifft后对信号的重建问题 2.调试出错问题 /* ...

  3. 文件解析库doctotext源码分析

    doctotext中没有make install选项,make后生成可执行文件 在buile目录下面有.so动态库和头文件,需要的可以从这里面拷贝 build/doctotext就是可执行程序. do ...

  4. muduo库net源码分析六(Socket 封装)

    Endian.h 封装了字节序转换函数(全局函数,位于muduo::net::sockets名称空间中). #ifndef MUDUO_NET_ENDIAN_H #define MUDUO_NET_E ...

  5. 2022最新中高级Android面试题目,网络相关+Android三方库的源码分析+数据结构与算法

    前言 最近有些朋友提问,Android QQ空间 换肤实现原理是什么?于是,我决定在这里做一下回答.对这个方面感兴趣的朋友也可以来看下. 手q的换肤机制主要是通过拦截系统resource中的sPrel ...

  6. sigslot库源码分析

    言归正传,sigslot是一个用标准C++语法实现的信号与槽机制的函数库,类型和线程安全.提到信号与槽机制,恐怕最容易想到的就是大名鼎鼎的Qt所支持的对象之间通信的模式吧.不过这里的信号与槽虽然在概念 ...

  7. DPDK 跟踪库tracepoint源码实例分析

    DPDK笔记 DPDK 跟踪库tracepoint源码实例分析 RToax 2021年4月 注意: 跟踪库 基于DPDK 20.05 DPDK跟踪库:trace library 1. trace流程源 ...

  8. print python 带回车_python标准库threading源码解读【二】

    紧接着上一篇文章继续解析源码 甘蔗:python标准库threading源码解读[一]​zhuanlan.zhihu.com 目录 Event的介绍和用法 Event源码解析 以后的内容尽量少一点并且 ...

  9. 从源码分析Android的Glide库的图片加载流程及特点

    转载:http://m.aspku.com/view-141093.html 这篇文章主要介绍了从源码分析Android的Glide库的图片加载流程及特点,Glide库是Android下一款人气很高的 ...

最新文章

  1. 单词转换(map对象)
  2. 组件间数据交互——父组件向子组件传值( props属性值类型) 子组件向父组件传值-携带参数 || 非父子组件间传值
  3. pipe row的用法, Oracle split 函数写法.
  4. linux php 上级目录,Linux目录架构详解_php
  5. 再次详解clientHeight、offsetHeight、scrollHeight
  6. 不丢包的 却花屏_用户观看视频业务出现花屏故障
  7. java jpanel对齐_java – 如何使用GridBagLayout在JPanel中对齐组件中心?
  8. Channel is reciprocal
  9. 论文查重率多少合格?
  10. Golang的chan阻塞测试
  11. android 音乐播放器关于歌词的处理
  12. rate-limiting
  13. React心得之降龙十八掌:第三式-见龙在田( 组件生命周期详解)
  14. 3dsmax uvw展开
  15. JavaBean为什么需要序列化?
  16. 计算机专业毕设评阅人评语,毕业论文评阅人评语模板
  17. 天黑请闭眼 杀人游戏 规则
  18. 复旦大学软件工程硕士博士学位点被撤销!整理20年被撤销计算机相关的学位点名单...
  19. Thread.sleep() 和 Thread.yield() 区别
  20. ubuntu18.04安装vim

热门文章

  1. 仿iPhone计算器(带括号,MVC)
  2. tar.xz和tar.bz(bz2)文件压缩与解压小记
  3. 前端实习生笔试_2019字节跳动前端实习生笔试面试
  4. 记本周的技能get!
  5. java字符串含有特殊字符_[Java教程]判断输入的字符串是否含有特殊字符和表情_星空网...
  6. 2021-2027全球与中国玻璃纤维预浸料市场现状及未来发展趋势
  7. 2021年山东省安全员C证考试报名及山东省安全员C证证考试
  8. 计算机绘图基础教程习题,《机械制图与Auto CAD基础教程习题集》
  9. Java基础积累:阻塞队列
  10. You can assume that two 网商奖金制度would not