MultiType 源码分析
前言
空闲时间不学习点什么真的是心慌慌的。这次就来分析分析 MultiType 这个三方库。挺不错的一个东西,也是我在好几个项目里用到的三方库。希望这个文章能够帮助到其他人吧,虽然希望渺茫~
MultiType 简介
MultiType 是对 RecyclerView 的 Adapter 的一个扩展,它把 面向 Adapter 编程变成了面向 ViewBinder 编程。之前的对于不同类型的 item 的处理,需要自己在 Adapter 中去手动区分手动处理,这样写出来的 Adapter 代码逻辑复杂不好维护,所有的 item 类型的处理都是在一块的;而 MultiType 对每种类型的 item,都有一个单独的 ViewBinder 去处理各自 item 的 ui 和代码逻辑,MultiType 通过继承并重写 RecyclerView 的 Adapter,把原本 Adapter 里边的对 item 的 ui 和 逻辑的处理,传递到了相应的 ViewBinder 中去处理,这样就把不同类型 item 的逻辑都单独封装在了 ViewBinder 里边。
这样处理的好处也是很明显的,它把不同类型的 item 都模块化了,便于不同类型 item 的修改替换组合。
MultiType 简单使用
-
在 gradle 文件里引用
implementation 'me.drakeet.multitype:multitype:3.5.0'
-
创建一个数据模型
public class TextItem { public final @NonNull String text; public TextItem(@NonNull String text) { this.text = text; } }
-
创建一个类(ViewBinder)继承自
ItemViewBinder<T, VH extends ViewHolder>
public class TextItemViewBinder extends ItemViewBinder<TextItem, TextItemViewBinder.TextHolder> { static class TextHolder extends RecyclerView.ViewHolder { private @NonNull final TextView text; TextHolder(@NonNull View itemView) { super(itemView); this.text = (TextView) itemView.findViewById(R.id.text); } } @NonNull @Override protected TextHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View root = inflater.inflate(R.layout.item_text, parent, false); return new TextHolder(root); } @Override protected void onBindViewHolder(@NonNull TextHolder holder, @NonNull TextItem textItem) { holder.text.setText("hello: " + textItem.text); Log.d("demo", "position: " + getPosition(holder)); Log.d("demo", "adapter: " + getAdapter()); } }
-
创建 MultiTypeAdapter 并注册 ViewBinder
public class SampleActivity extends AppCompatActivity { private MultiTypeAdapter adapter; private Items items; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list); adapter = new MultiTypeAdapter(); adapter.register(TextItem.class, new TextItemViewBinder()); adapter.register(ImageItem.class, new ImageItemViewBinder()); adapter.register(RichItem.class, new RichItemViewBinder()); recyclerView.setAdapter(adapter); TextItem textItem = new TextItem("world"); ImageItem imageItem = new ImageItem(R.mipmap.ic_launcher); RichItem richItem = new RichItem("小艾大人赛高", R.mipmap.avatar); items = new Items(); for (int i = 0; i < 20; i++) { items.add(textItem); items.add(imageItem); items.add(richItem); } adapter.setItems(items); adapter.notifyDataSetChanged(); } }
MultiType 源码分析
MultiType 的工作流程分析(一对一)
我们一般情况下对 RecyclerView 的 item 的处理都是在 Adapter 里边处理的,但是 MultiType 却是在 ViewBinder 里边处理的,它是怎么把逻辑转换到 Viewbinder 里的?看了上边的简单使用的介绍,它实例化了 MultiTypeAdapter 这个适配器并且设置给了 RecyclerView,这个操作是常规操作,但是 item 逻辑就是到 ViewBinder 里边去了;再看这个 adapter,它提供了注册 ViewBinder 的方法,看上去这个 MultiTypeAdapter
好像并不简单,那就从它开始突破吧。。
看它的构造方法:
public class MultiTypeAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final String TAG = "MultiTypeAdapter";
private @NonNull List<?> items;
private @NonNull TypePool typePool;
/**
* Constructs a MultiTypeAdapter with an empty items list.
*/
public MultiTypeAdapter() {
this(Collections.emptyList());
}
/**
* Constructs a MultiTypeAdapter with a items list.
*
* @param items the items list
*/
public MultiTypeAdapter(@NonNull List<?> items) {
this(items, new MultiTypePool());
}
/**
* Constructs a MultiTypeAdapter with a items list and an initial capacity of TypePool.
*
* @param items the items list
* @param initialCapacity the initial capacity of TypePool
*/
public MultiTypeAdapter(@NonNull List<?> items, int initialCapacity) {
this(items, new MultiTypePool(initialCapacity));
}
/**
* Constructs a MultiTypeAdapter with a items list and a TypePool.
*
* @param items the items list
* @param pool the type pool
*/
public MultiTypeAdapter(@NonNull List<?> items, @NonNull TypePool pool) {
checkNotNull(items);
checkNotNull(pool);
this.items = items;
this.typePool = pool;
}
...
}
可以看到,这几个构造方法最终都会调用 MultiTypeAdapter(@NonNull List<?> items, @NonNull TypePool pool)
这个构造方法。这里以调用无参构造方法为例,当最终调用到此构造方法的时候,先判断参数合法性,然后 保存 数据集合 items 和 MultiTypePool 的实例
接下来再来看一下 MultiTypeAdapter#register(@NonNull Class<? extends T> clazz, @NonNull ItemViewBinder<T, ?> binder)
方法:
public <T> void register(@NonNull Class<? extends T> clazz, @NonNull ItemViewBinder<T, ?> binder) {
checkNotNull(clazz);
checkNotNull(binder);
checkAndRemoveAllTypesIfNeeded(clazz);
register(clazz, binder, new DefaultLinker<T>());
}
<T> void register(
@NonNull Class<? extends T> clazz,
@NonNull ItemViewBinder<T, ?> binder,
@NonNull Linker<T> linker) {
typePool.register(clazz, binder, linker);
binder.adapter = this;
}
这个方法里,主要是调用 MultiTypePool 的 register 方法去注册 viewBinder,并且把 adapter 保存到 ViewBinder 里。
这里还有个需要注意的地方,checkAndRemoveAllTypesIfNeeded(clazz);
这个方法,会调用 MultiTypePool 的 unregister 方法,如果取消注册成功了,说明之前的实体类已经绑定过 ViewBinder 了,如果再注册这个实体类的话,之前的绑定会被删除,然后绑定新的 ViewBinder。
这里先简单介绍一下 MultiTypePool,他是一个类型池,里边维护了三个数组,分别保存数据模型的类型、与数据模型绑定的 Viewbinder、链接前边两者的一个数据类型对象。ViewBinder 的注册和取消注册其实就是对 MultiTypePool 里这三个数组的添加删除操作。
我们都知道,对一个常规的 RecyclerView 的 Adapter 的处理流程:实现 onCreateViewHolder、onBindViewHolder、getItemViewType、getItemCount。接下来看看 MultiTypeAdapter 是怎么处理这几个方法的:
@Override
public final int getItemViewType(int position) {
Object item = items.get(position);
return indexInTypesOf(position, item);
}
int indexInTypesOf(int position, @NonNull Object item) throws BinderNotFoundException {
int index = typePool.firstIndexOf(item.getClass());
if (index != -1) {
@SuppressWarnings("unchecked")
Linker<Object> linker = (Linker<Object>) typePool.getLinker(index);
return index + linker.index(position, item);
}
throw new BinderNotFoundException(item.getClass());
}
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ItemViewBinder<?, ?> binder = typePool.getItemViewBinder(indexViewType);
return binder.onCreateViewHolder(inflater, parent);
}
@Override @Deprecated
public final void onBindViewHolder(@NonNull ViewHolder holder, int position) {
onBindViewHolder(holder, position, Collections.emptyList());
}
@Override @SuppressWarnings("unchecked")
public final void onBindViewHolder(ViewHolder holder, int position, @NonNull List<Object> payloads) {
Object item = items.get(position);
ItemViewBinder binder = typePool.getItemViewBinder(holder.getItemViewType());
binder.onBindViewHolder(holder, item, payloads);
}
@Override
public final int getItemCount() {
return items.size();
}
先来看一下 getItemViewType
这个方法的处理,它是设置 item 对应的类型标志的,它调用了 indexInTypesOf
方法,在这个方法里,首先获取数据模型类型在对应集合中的第一个索引;然后再从 Linker 中获取到数据模型类型与 ViewBinder 的代表对应关系的数字;然后返回对应关系数字和索引的和,这两个数字的和就表示这 item 的类型标志。这里边有一点需要注意,在一对一对应关系中, Linker 的默认实现是 DefaultLinker
,DefaultLinker#index
方法始终返回 0,所以可以看出 getItemViewType
返回的 item 类型标志其实就是 item 的数据类型在 MultiTypePool 里的数据集合里的索引值。
再看 onCreateViewHolder、onBindViewHolder
方法,这两个本来处理 recyclerView 的 item 的 ui 和逻辑的地方,在这里直接调用了对应的不同类型 item 的 ViewBinder 里边相应的方法,很巧妙的把不同类型的 item 的处理分隔开了。注意 ViewBinder 的 onBindViewHolder 方法,支持了 payloads 参数,意味着它也提供了对局部刷新的支持。getItemCount
返回数据集合的 size,这个没有啥变化。
由于原来要在 Adapter 中处理的逻辑现在都要放在 ViewBinder 里边处理了,MultiType 也使 ViewBinder 支持了一些 Adapter 里的方法:
@Override @SuppressWarnings("unchecked")
public final void onViewRecycled(@NonNull ViewHolder holder) {
getRawBinderByViewHolder(holder).onViewRecycled(holder);
}
@Override @SuppressWarnings("unchecked")
public final boolean onFailedToRecycleView(@NonNull ViewHolder holder) {
return getRawBinderByViewHolder(holder).onFailedToRecycleView(holder);
}
@Override @SuppressWarnings("unchecked")
public final void onViewAttachedToWindow(@NonNull ViewHolder holder) {
getRawBinderByViewHolder(holder).onViewAttachedToWindow(holder);
}
@Override @SuppressWarnings("unchecked")
public final void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
getRawBinderByViewHolder(holder).onViewDetachedFromWindow(holder);
}
到这里 MultiType 这个 RecyclerView 的潘多拉魔盒也打开的差不多了,这里来个小总结加深一下印象:ViewBinder 通过 MultiType 内部的 MultiTypeAdapter 的 register 方法注册到类型池 MultiTypePool 中;MultiType 内部的 MultiTypeAdapter 在 onCreateViewHolder、onBindViewHolder
方法里直接从类型池中获取到相应的 ViewBinder 并调用相应的方法,把 item 逻辑转移到 ViewBinder 中。
TypePool 的作用
它是一个接口,定义了类型池的一些必要的方法:
public interface TypePool {
<T> void register(
@NonNull Class<? extends T> clazz,
@NonNull ItemViewBinder<T, ?> binder,
@NonNull Linker<T> linker);
boolean unregister(@NonNull Class<?> clazz);
int size();
int firstIndexOf(@NonNull Class<?> clazz);
@NonNull Class<?> getClass(int index);
@NonNull ItemViewBinder<?, ?> getItemViewBinder(int index);
@NonNull Linker<?> getLinker(int index);
}
它的实现类是 MultiTypePool,下边分析一下 MultiTypePool 这个类:
先看它的构造方法:
private final @NonNull List<Class<?>> classes;
private final @NonNull List<ItemViewBinder<?, ?>> binders;
private final @NonNull List<Linker<?>> linkers;
public MultiTypePool() {
this.classes = new ArrayList<>();
this.binders = new ArrayList<>();
this.linkers = new ArrayList<>();
}
public MultiTypePool(int initialCapacity) {
this.classes = new ArrayList<>(initialCapacity);
this.binders = new ArrayList<>(initialCapacity);
this.linkers = new ArrayList<>(initialCapacity);
}
public MultiTypePool(
@NonNull List<Class<?>> classes,
@NonNull List<ItemViewBinder<?, ?>> binders,
@NonNull List<Linker<?>> linkers) {
checkNotNull(classes);
checkNotNull(binders);
checkNotNull(linkers);
this.classes = classes;
this.binders = binders;
this.linkers = linkers;
}
构造方法里主要是初始化 classes、binders、linkers 这三个集合。
接着看它的 register
方法,前边分析的时候,当 adapter 调用 register 方法去注册 ViewBinder 的时候,最终调用的就是 MultiTypePool 的 register 方法:
@Override
public <T> void register(
@NonNull Class<? extends T> clazz,
@NonNull ItemViewBinder<T, ?> binder,
@NonNull Linker<T> linker) {
checkNotNull(clazz);
checkNotNull(binder);
checkNotNull(linker);
classes.add(clazz);
binders.add(binder);
linkers.add(linker);
}
它做的事情很简单,检查参数合法性后,把数据放入相应的集合里。
unregister
方法就是把相应的数据从集合里移除;size
方法返回数据模型类型集合的大小;getClass、getItemViewBinder、getLinker
方法是根据索引获取三个集合里对应的数据。
下边看一下 firstIndexOf
方法:
@Override
public int firstIndexOf(@NonNull final Class<?> clazz) {
checkNotNull(clazz);
//拿到数据模型类型在集合中的索引值
int index = classes.indexOf(clazz);
if (index != -1) {
return index;
}
//如果数据模型类型在集合中不存在,就找集合中是否存在它的父类,或它实现的接口
for (int i = 0; i < classes.size(); i++) {
if (classes.get(i).isAssignableFrom(clazz)) {
return i;
}
}
return -1;
}
有一点需要注意,classes.indexOf(clazz)
获取的是集合中第一个 clazz 的索引值,如果有相同的 clazz,这里返回的也是第一个 clazz 的索引值。
在获取索引的时候,如果集合中有它的父类或者它实现的接口,也是可以获取到索引值的,这一点比较奇特。
到此 MultiTypePool 这个类就分析完了,它的主要作用就是对注册的 ViewBinder 保存记录起来,对取消注册的 ViewBinder 从记录里边把它移除。
MultiType 一对多的实现
先看一下 MultiType 一个数据模型对应多个 ViewBinder 的注册时的写法:
//方式1:实现 Linker 接口,根据数据模型里的类型字段返回相应的 ViewBinder 索引值
//,这个索引值与前边的 to 方法传入 ViewBinder 的位置对应
adapter.register(Data.class).to(
new DataType1ViewBinder(),
new DataType2ViewBinder()
).withLinker((position, data) ->
data.type == Data.TYPE_2 ? 1 : 0
);
//方式2:实现 ClassLinker 接口,根据数据模型里的类型字段返回相应的 ViewBinder 的 class 对象
adapter.register(Data.class).to(
new DataType1ViewBinder(),
new DataType2ViewBinder()
).withClassLinker((position, data) -> {
if (data.type == Data.TYPE_2) {
return DataType2ViewBinder.class;
} else {
return DataType1ViewBinder.class;
}
});
看了上边的一对多 ViewBinder 是不是有点懵,没事咱接着慢慢分析,先看看 MultiTypeAdapter 的 register(@NonNull Class<? extends T> clazz)
:
@CheckResult
public @NonNull <T> OneToManyFlow<T> register(@NonNull Class<? extends T> clazz) {
checkNotNull(clazz);
checkAndRemoveAllTypesIfNeeded(clazz);
return new OneToManyBuilder<>(this, clazz);
}
这里直接返回了 OneToManyBuilder
这个类的实例,那么接下来就看看 OneToManyBuilder
是个啥:
构造方法:
OneToManyBuilder(@NonNull MultiTypeAdapter adapter, @NonNull Class<? extends T> clazz) {
this.clazz = clazz;
this.adapter = adapter;
}
保存了数据模型类型的实例和 MultiTypeAdapter 的实例。
上边一对多注册的时候,调用了 OneToManyBuilder#to(@NonNull ItemViewBinder<T, ?>... binders)
:
@Override @CheckResult @SafeVarargs
public final @NonNull OneToManyEndpoint<T> to(@NonNull ItemViewBinder<T, ?>... binders) {
checkNotNull(binders);
this.binders = binders;
return this;
}
把一对多的这个 ViewBinder 的集合保存到 OneToManyBuilder
中。to 方法返回的是 OneToManyEndpoint 这个接口,这个接口里只有两个方法: withLinker、withClassLinker
,看它在 OneToManyBuilder
中的实现:
@Override
public void withLinker(@NonNull Linker<T> linker) {
checkNotNull(linker);
doRegister(linker);
}
@Override
public void withClassLinker(@NonNull ClassLinker<T> classLinker) {
checkNotNull(classLinker);
doRegister(ClassLinkerWrapper.wrap(classLinker, binders));
}
private void doRegister(@NonNull Linker<T> linker) {
for (ItemViewBinder<T, ?> binder : binders) {
adapter.register(clazz, binder, linker);
}
}
上边方法最终调用了 doRegister
把 ViewBinder 通过 adapter 的 register 方法注册到类型池。需要注意的是,这个三参数的 register 方法,没有调用 checkAndRemoveAllTypesIfNeeded(clazz);
,所以它在注册的时候,不会删除之前的已注册,这样就实现了一对多。
有一些细节地方还是要提一下的,在分析一对一流程的时候有个调用链 MultiTypeAdapter#getItemViewType(int position) -> MultiTypeAdapter#indexInTypesOf(int position, @NonNull Object item)
,这里是设置对应位置 item 的类型标志的,最终返回的值是 数据模型类型在集合中的第一个索引值 + Linker#index 返回的值
,这个处理真的是很巧妙了,要实现一对多,只是用数据模型类型在集合中的第一个索引值肯定是实现不了的,所以 Linker 就派上用场了,自己实现 Linker 的 index 方法,在设置 item 类型的方法中,就能准确的返回当前的索引值,即类型标志了。
自己实现 Linker 的 index
方法时,应该怎么返回这个值?从 OneToManyBuilder#doRegister
这个方法的实现可以看出,当注册一对多时,对应的这多个 ViewBinder 是通过循环遍历 OneToManyBuilder
中的 binders 集合去注册的,所以注册的顺序和 binders 集合保存的顺序相同,所以在实现 index 方法时,直接返回 ViewBinder 对应的索引就行了,当调用 getItemViewType
方法时,刚好返回的是(类型池中第一个这个类型的索引 + OneToManyBuilder 中 binders 需要使用的 ViewBinder 对应的索引),这就保证了一对多时 item 类型是不同的了。
上边介绍了一对多注册的两种方式,withLinker
这个应该已经清楚了,再看看第二个写法 withClassLinker
。它不同于方式一的就是,在 index 方法返回的不是索引值,而是 ViewBinder 的 class 实例,那方式二是怎么得到正确的索引值的呢?再回到OneToManyBuilder#withClassLinker(@NonNull ClassLinker<T> classLinker)
方法中,发现它在调用 doRegister
方法时,传入的参数是一个 ClassLinkerWrapper
的实例,它的实现很简单,它实现了 Linker 接口,在 index 方法里通过 ClassLinker#index
方法获取到对应的 ViewBinder,然后从 ClassLinkerWrapper 的 binders 集合里获取到对应的索引值返回,
Linker 的作用
其实从上边一路分析下来,Linker 的作用已经交代的非常清楚了,它是为了实现一对多而设计的,由于一对多时,类型池里边的集合数据会有重复地出现,这时候获取索引值就可能会不准确了(只能获取到相同数据的第一个索引值),这时候再通过 Linker 去设置相同数据模型类型对应的不同 ViewBinder 的索引值,两个索引值相加就获取到了正确的值。
总结
林林总总的也算是分析完了,自我感觉收获挺多,当然在这个过程中也能看出自己的不足。比如文字描述能力。。其实自我感觉写的是有点混乱的,文字表达能力有点差,这方面还是需要再加强一下。。。