背景
- 前面一篇文章介绍了如何使用ConcatAdapter,本篇文章将围绕ConcatAdapter是如何做到合并多个Adapter。(超级缝合怪)
ConcatAdapter源码分析
- 分析新的类时候,特别是那个有add和remove的,应该先从这两个地方下手。我们先分析下如何添加和删除的吧。
ConcatAdapter如何添加和移除
public boolean addAdapter(int index, @NonNull Adapter<? extends ViewHolder> adapter) {
return mController.addAdapter(index, (Adapter<ViewHolder>) adapter);
}
public boolean removeAdapter(@NonNull Adapter<? extends ViewHolder> adapter) {
return mController.removeAdapter((Adapter<ViewHolder>) adapter);
}
看了上面代码,可以基本确定ConcatAdapter是一层壳,所有的逻辑操作基本都是在Controller里面完成的,所以我们着重看Controller(ConcatAdapterController)即可。
ConcatAdapterController是什么?
介绍介绍下:ConcatAdapterController是操作多个Adapter聚合到一个RecyclerView的控制类,基本信息包含:存储多个Adapter的数据结构(NestedAdapterWrapper)、查找不同Adapter的位置的数据结构(ViewTypeStorage)等。但这里主要拿这两个来分析大概就能让大家理解ConcatAdapter如何运作的。为了引出下面NestedAdapterWrapper的作用,我们先看一段add的代码。
boolean addAdapter(int index, Adapter<ViewHolder> adapter) {
……………………
NestedAdapterWrapper existing = findWrapperFor(adapter);
//查找NestedAdapterWrapper,虽然传递进来的是Adapter,但实际上会封装成NestedAdapterWrapper来进行核心逻辑交互
if (existing != null) {
return false;
}
NestedAdapterWrapper wrapper = new NestedAdapterWrapper(adapter, this,
mViewTypeStorage, mStableIdStorage.createStableIdLookup());
mWrappers.add(index, wrapper);
// notify attach for all recyclerview(你传递来的新的adapter要重新attach上已经和ConcatAdapter建立好连接的RecyclerView上)
for (WeakReference<RecyclerView> reference : mAttachedRecyclerViews) {
RecyclerView recyclerView = reference.get();
if (recyclerView != null) {
adapter.onAttachedToRecyclerView(recyclerView);
}
}
…………………………
return true;
}
NestedAdapterWrapper作用
- 由上面的代码可以看出,每个通过新添加的adapter最后都会被包装成一个NestedAdapterWrapper。NestedAdapterWrapper可以说是整个ConcatAdapter的核心,现在说明下构造函数里面的参数作用。
- adapter:持有adapter引用,后面的数据刷新都是通过Wrapper获取adapter去操作的。
- callBack:这个参数就是整一套设计的精髓所在,该callBack是回调所有添加到ConcatAdapter里的Adapter在数据层面的回调。详情看下面的《监听不同Adapter数据层变化》
- viewTypeStorage:adapter存储的容器,其中该设计从RecyclerView角度上来看是总体的Adapter,但从ConcatAdapterController角度来说是从总体里面找出每个Adapter的位置,所以说ViewTypeStorage是一个值得一讲的东西。详情看下面《ViewTypeStorage神奇设计》
- stableIdLookup:这个参数就是提供查找不同adapter对于RecyclerView的位置的工具。多嘴提一句,工程师很友好的命名让我们能更好的理解其中作用。例如localToGlobal,含义就是普通adapter对于ConcatAdapter的位置信息。(🐂🍺)
- 简单讲解下Wrapper内部原理:在实例化的时候,创建RecyclerView.AdapterDataObserver,给当前的adapter注册一个数据变化的回调,因为创建Wrapper后会把adapter attach到RecyclerView上,所以回调会在RecyclerView数据源发送变化时候回调相应接口,然后接口会通过callBack回调回到Controller去处理。
private RecyclerView.AdapterDataObserver mAdapterObserver =
…………………………………………………………
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mCachedItemCount += itemCount;
mCallback.onItemRangeInserted(
NestedAdapterWrapper.this,
positionStart,
itemCount);
if (mCachedItemCount > 0
&& adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) {
mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this);
}
}
…………………………………………………………
};
提示:Wrapper里面还有个mCachedItemCount私有属性,该属性记录当前adapter有多少个ViewHolder,在下面介绍《监听不同Adapter数据层变化》起着重要作用。
监听不同Adapter数据层变化
上面Wrapper都说了所有的adapter数据层面变化,都会抛到Controller是实现具体逻辑,所以回到ConcatAdapterController里面去查看某个adapter数据层面变化后,如何同步到整体的。我们下面只看数据插入来,其他的都类似,我们要懂的是核心设计思想。
//发送数据变化的adapter通过callBack回调回来的
@Override
public void onItemRangeInserted(@NonNull NestedAdapterWrapper nestedAdapterWrapper,
int positionStart, int itemCount) {
//从Wrapper
final int offset = countItemsBefore(nestedAdapterWrapper);
mConcatAdapter.notifyItemRangeInserted(
positionStart + offset,
itemCount
);
}
//从当前Controller存储的Wrapper中查找当前发送数据变化的Wrapper(wrapper即adapter,只不过二次封装下)
//找到当前变化的adapter并且返回此adapter之前的holder个数
private int countItemsBefore(NestedAdapterWrapper wrapper) {
int count = 0;
for (NestedAdapterWrapper item : mWrappers) {
if (item != wrapper) {
count += item.getCachedItemCount();
} else {
break;
}
}
return count;
}
上述代码可以看出,通过countItemsBefore函数获取发送变化的adapter之前有多少个ViewHolder,这个获取ViewHolder数量正是上面提过的mCachedItemCount,每个Wrapper都可以通过接口返回mCachedItemCount数值。核心思想:当前变化adapter开始变化的index+获取排在adapter前面ViewHolder的数量,调用adapter接口,告知RecyclerView刷新哪部分区域的视图。(妙,真的妙)
ViewTypeStorage神奇设计
上面我们分析了ConcatAdapter里面子adapter数据源发送改变,是如何通知RecyclerView去刷新的了。那么我们还有最重要一步就是创建ViewHolder和给ViewHolder绑定数据的?这里就涉及到ViewTypeStorage的神奇地方,从名字都可以看出这个是不同类型的View存储结构了。
一、如何从多个Adapter中返回各自ViewType
下面findWrapperAndLocalPosition核心算法就是通过全局的position去算出对应子Adapter的position,虽然算法不难,但是设计简单合理,挺棒的。获取到对应的adapter和position后,通过不同策略生成与子Adapter的本地ViewType唯一映射关系的全局itemViewType,然后返回给RecyclerView。
提示:不同策略的查询与存储ViewType都是实现了ViewTypeLookup接口的,有兴趣可以自己去看下。这里的本地和全局可以理解成:本地ViewType是从子Adapter角度来看type类型,全局ViewType是从RecyclerView的角度来看type类型。
//globalPosition就是对于RecyclerView来说整体的位置
public int getItemViewType(int globalPosition) {
//拿到了全局position对应的adapter信息
WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
//根据子Adapter的viewType,递增生成唯一的globalType并且存储着对应的映射关系
int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
……
return itemViewType;
}
//根据返回的整体position,算出整体position落到哪个adapter上面,计算出对于该adapter的哪个位置,封装一下进行返回
private WrapperAndLocalPosition findWrapperAndLocalPosition(
int globalPosition
) {
WrapperAndLocalPosition result;
……………………
for (NestedAdapterWrapper wrapper : mWrappers) {
if (wrapper.getCachedItemCount() > localPosition) {
result.mWrapper = wrapper;
result.mLocalPosition = localPosition;
break;
}
localPosition -= wrapper.getCachedItemCount();
}
……………………
return result;
}
二、如何创建ViewHolder
- 因为ConcatAdapter的createViewHolder直接个传递参数,所以我们直接看Controller的onCreateViewHolder。上面已经从ViewTypeLookup返回了全局唯一的ViewType,那么剩下就是要看下怎么从唯一的ViewType转换成真实的adapter的ViewType了。
- Controller的onCreateViewHolder方法实际上只是从存储Wrapper地方,通过全局的viewType找到Wrapper,拿到关联的子Adapter的信息而已。getWrapperForGlobalType 函数只是从mapping映射里直接获取了Wrapper对象而已。该对象的存储在getItemViewType函数调用时候就已经把映射关系存放进去了。
//Controller的onCreateViewHolder
public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);
return wrapper.onCreateViewHolder(parent, globalViewType);
}
//IsolatedViewTypeStorage类的getWrapperForGlobalType方法,直接获取Wrapper对象
public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
NestedAdapterWrapper wrapper = mGlobalTypeToWrapper.get(
globalViewType);
………………
return wrapper;
}
- 上面既然拿到了我们的Wrapper对象,那什么数据我们都有了,接下来我们看看怎样根据全局viewType,怎么回调到子Adapter的onCreateViewHolder吧。
//wrapper的onCreateViewHolder
ViewHolder onCreateViewHolder(
ViewGroup parent,
int globalViewType) {
int localType = mViewTypeLookup.globalToLocal(globalViewType);
return adapter.onCreateViewHolder(parent, localType);
}
可以从上面看出,其实比较简单,因为LookUp在getItemViewType时候已经建立了全局与本地的映射关系了,所以直接从数据中通过key获取真实的子Adapter的viewType就可以了,然后通过adapter去回调到开发者的代码上。是不是设计上超给力的👍👍👍
三、如何绑定ViewHolder
绑定的代码实际和创建相差不大,这里留给读者自行去看,比较有自己的收获
总结
- 官方新推出的ConcatAdapter,也在RecyclerView源码层做了很多工作的,这部分可能讲起来没十天十夜都讲不完,所以这篇文章不会从RecyclerView内部去讲解。
- ConcatAdapter非常好用,特别是能将一个列表拆分成多个模块,这样从设计角度来说更为合理,从之前的数值区分样式,变成模块划分,模块划分完再细分成样式。真心不错👍
- 大家如果想接入使用的话,可以参加我的另外篇文章:https://www.jianshu.com/p/21a2b004fc96
网友评论