空闲时间不学习点什么真的是心慌慌的。这次就来分析分析 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 的修改替换组合。
在 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
public class TextItemViewBinder extends ItemViewBinder {
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();
}
}
我们一般情况下对 RecyclerView 的 item 的处理都是在 Adapter 里边处理的,但是 MultiType 却是在 ViewBinder 里边处理的,它是怎么把逻辑转换到 Viewbinder 里的?看了上边的简单使用的介绍,它实例化了 MultiTypeAdapter 这个适配器并且设置给了 RecyclerView,这个操作是常规操作,但是 item 逻辑就是到 ViewBinder 里边去了;再看这个 adapter,它提供了注册 ViewBinder 的方法,看上去这个 MultiTypeAdapter
好像并不简单,那就从它开始突破吧。。
看它的构造方法:
public class MultiTypeAdapter extends RecyclerView.Adapter {
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
方法:
public void register(@NonNull Class extends T> clazz, @NonNull ItemViewBinder binder) {
checkNotNull(clazz);
checkNotNull(binder);
checkAndRemoveAllTypesIfNeeded(clazz);
register(clazz, binder, new DefaultLinker());
}
void register(
@NonNull Class extends T> clazz,
@NonNull ItemViewBinder binder,
@NonNull Linker 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
先来看一下 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 中。
它是一个接口,定义了类型池的一些必要的方法:
public interface TypePool {
void register(
@NonNull Class extends T> clazz,
@NonNull ItemViewBinder binder,
@NonNull Linker 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> classes;
private final @NonNull List> binders;
private final @NonNull List> 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> classes,
@NonNull List> binders,
@NonNull List> 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 void register(
@NonNull Class extends T> clazz,
@NonNull ItemViewBinder binder,
@NonNull Linker 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 一个数据模型对应多个 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 OneToManyFlow 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
:
@Override @CheckResult @SafeVarargs
public final @NonNull OneToManyEndpoint to(@NonNull ItemViewBinder... binders) {
checkNotNull(binders);
this.binders = binders;
return this;
}
把一对多的这个 ViewBinder 的集合保存到 OneToManyBuilder
中。to 方法返回的是 OneToManyEndpoint 这个接口,这个接口里只有两个方法: withLinker、withClassLinker
,看它在 OneToManyBuilder
中的实现:
@Override
public void withLinker(@NonNull Linker linker) {
checkNotNull(linker);
doRegister(linker);
}
@Override
public void withClassLinker(@NonNull ClassLinker classLinker) {
checkNotNull(classLinker);
doRegister(ClassLinkerWrapper.wrap(classLinker, binders));
}
private void doRegister(@NonNull Linker linker) {
for (ItemViewBinder 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
方法中,发现它在调用 doRegister
方法时,传入的参数是一个 ClassLinkerWrapper
的实例,它的实现很简单,它实现了 Linker 接口,在 index 方法里通过 ClassLinker#index
方法获取到对应的 ViewBinder,然后从 ClassLinkerWrapper 的 binders 集合里获取到对应的索引值返回,
其实从上边一路分析下来,Linker 的作用已经交代的非常清楚了,它是为了实现一对多而设计的,由于一对多时,类型池里边的集合数据会有重复地出现,这时候获取索引值就可能会不准确了(只能获取到相同数据的第一个索引值),这时候再通过 Linker 去设置相同数据模型类型对应的不同 ViewBinder 的索引值,两个索引值相加就获取到了正确的值。
林林总总的也算是分析完了,自我感觉收获挺多,当然在这个过程中也能看出自己的不足。比如文字描述能力。。其实自我感觉写的是有点混乱的,文字表达能力有点差,这方面还是需要再加强一下。。。