MultiType 能够简单,灵活的为RecyclerView实现多类型列表。java
MultiType介绍:juejin.im/post/59702b…git
MultiType源码:github.com/drakeet/Mul…github
一、建立RecyclerView 的数据 Java Beanbash
data class TextItem(val text : String)
复制代码
二、建立TextItemViewBinder,继承自ItemViewBinder。 相似于ViewHolder数据结构
class TextItemViewBinder : ItemViewBinder<TextItem, TextItemViewBinder.TextHolder>() {
class TextHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val text: TextView
init {
text = itemView.findViewById<View>(R.id.text) as TextView
}
}
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): TextHolder {
val root = inflater.inflate(R.layout.item_text, parent, false)
return TextHolder(root)
}
override fun onBindViewHolder(holder: TextHolder, textItem: TextItem) {
holder.text.text = "hello: " + textItem.text
}
}
复制代码
三、使用adapter 的 register()方法绑定ItemBinder 和 对应的 Java Bean。app
class TestActivity : AppCompatActivity() {
private var adapter : MultiTypeAdapter? = null
private var items : Items? = null
private var recyclerView : RecyclerView? = null;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
recyclerView = findViewById<View>(R.id.list) as RecyclerView
adapter = MultiTypeAdapter();
adapter!!.register(TextItem::class.java, TextItemViewBinder()) // TextItem 和 TextItemViewBinder 绑定
adapter!!.register(ImageItem::class.java, ImageItemViewBinder()) // ImageItem 和 ImageItemViewBinder 绑定
adapter!!.register(RichItem::class.java, RichItemViewBinder()) // RichItem 和 RichItemViewBinder 绑定
recyclerView?.adapter = adapter
items!!.add(TextItem("world"))
items!!.add(ImageItem(R.mipmap.ic_launcher))
items!!.add(RichItem("小艾大人赛高", R.drawable.img_11))
adapter?.items = items as Items
adapter?.notifyDataSetChanged()
}
}
复制代码
一、实现一对多绑定,即一种Java Bean 绑定对个ViewHolder。框架
adapter.register(Data.class).to(
new DataType1ViewBinder(),
new DataType2ViewBinder()
).withClassLinker((position, data) -> {
if (data.type == Data.TYPE_2) {
return DataType2ViewBinder.class; // 当data.type为Data.TYPE_2 时,使用DataType2ViewBinder
} else {
return DataType1ViewBinder.class; // 不然,使用DataType1ViewBinder
}
});
复制代码
二、供如下方法进行重写ide
protected long getItemId(@NonNull T item)
protected void onViewRecycled(@NonNull VH holder) // ViewHolder 被回收时调用
protected boolean onFailedToRecycleView(@NonNull VH holder) // ViewHolder 建立失败时调用
protected void onViewAttachedToWindow(@NonNull VH holder) // Attach时调用
protected void onViewDetachedFromWindow(@NonNull VH holder) // Detach时调用
复制代码
注:源码分析针对 3.X 分支源码分析
先来一张比较直观的图吧, 否则看源码很懵逼:布局
注:这张图只是为了理解更直观一些,其实底层是先拿到 JavaBean Class 才得到 ViewType 的 Position。
源码本质是,MultiTypeAdapter在渲染多类型布局的时候,根据Adapter 的数据集合拿到第i个数据的JavaBean Class,而后根据这个Classs, 拿到ViewType 。以后onCreateViewHolder方法和 onBindViewHolder方法根据这个View Type 拿到对应的Binder,而后让相应的Binder来进行绑定布局和绑定数据。
框架底层维护了一份映射表,这份映射表对应 ViewType ,JavaBean Class, Linker, ItemViewBinder的四重绑定。
内部映射的数据结构:
private final @NonNull List<Class<?>> classes; // Java Bean Class
private final @NonNull List<ItemViewBinder<?, ?>> binders; // 相似于ViewHolder
private final @NonNull List<Linker<?>> linkers; // 实现 一对一绑定,一对多绑定的帮助类
复制代码
映射表举例:
ViewType | Java Bean Class | Linker | ItemViewBinder |
---|---|---|---|
0 | C1 | L1 | binder_1 |
1 | C2 | L2 | binder_2_1 |
2 | C2 | L2 | binder_2_2 |
3 | C2 | L2 | binder_2_3 |
4 | C2 | L3 | binder_3_1 |
源码本质是,MultiTypeAdapter在渲染多类型布局的时候,根据Adapter 的数据集合拿到第i个数据的JavaBean Class,而后根据这个Classs, 拿到ViewType 。以后onCreateViewHolder方法和 onBindViewHolder方法根据这个View Type 拿到对应的Binder,而后让相应的Binder来进行绑定布局和绑定数据。
相信看到这个表以后,会对上面的话理解更清晰一些。
好了,有了前面这些铺垫,理解下面的内容会容易不少。
一、首先获取ViewHolder的 Type,而后获得ViewHolder
// MultiTypeAdapter.java
@Override
public final int getItemViewType(int position) {
Object item = items.get(position); // 获取数据集合中第position个的数据
return indexInTypesOf(position, item); // 查找ViewType
}
int indexInTypesOf(int position, @NonNull Object item) throws BinderNotFoundException {
int index = typePool.firstIndexOf(item.getClass()); // 获取 item 对应的 Class 类的 在映射表中第一次出现的位置
if (index != -1) {
@SuppressWarnings("unchecked")
Linker<Object> linker = (Linker<Object>) typePool.getLinker(index);
return index + linker.index(position, item); // linker.index(position, item) 一对多绑定的索引是如何查找的,这个后面分析
/*
type类型
类在映射表中的位置 + 一对多注册的索引
若是没有一对多绑定,linker.index(position, item)为0,则就是类在映射表中的第一次出现的位置
若是有一对多绑定时, type整形为, 类在映射表中第一次出现的位置 + 一对多注册 对应的 索引
这样就实现了position 和 对应 ItemViewType 的映射
*/
}
throw new BinderNotFoundException(item.getClass());
}
复制代码
二、调用 onCreaterViewHolder 和 onBindViewHolder 来 绑定布局和填充数据。
// MultiTypeAdapter.java
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ItemViewBinder<?, ?> binder = typePool.getItemViewBinder(indexViewType); // 根据第一步拿到的indexViewType ,获取到与之相对应的 ItemViewBinder
return binder.onCreateViewHolder(inflater, parent); // 调用 ItemViewBinder 的 onCreateViewHolder,用于绑定布局。至关于下发
}
@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()); // 同理拿到对应的ItemViewBinder
binder.onBindViewHolder(holder, item, payloads); // 调用 ItemViewBinder 的 onBindViewHolder,用于绑定数据。至关于下发
}
复制代码
三、如何绑定,也就是如何填充上面的数据映射表
一对一绑定的实现方式:
// MultiTypePool.java
@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);
}
// 其实就是在上面的映射表中插入一行数据,增长行映射。
复制代码
一对多绑定的实现方式:
首先看一下一对多绑定的写法:
// 一对多绑定方式
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;
}
});
复制代码
看一下 withClassLinker()方法。
// OneToManyBuilder.java
@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); // 这块其实就是就是遍历同一个Linker绑定的ItemViewBinder, 而后将 Class, Binder, Linker 插入MultiTypePool 所维护的映射表中,实现映射
}
}
复制代码
四、一对多绑定的索引是如何查找的
// ClassLinkerWrapper.java
@Override
public int index(int position, @NonNull T t) {
Class<?> userIndexClass = classLinker.index(position, t); // 这个方法是用户实现一对多绑定时,定义的。
for (int i = 0; i < binders.length; i++) {
if (binders[i].getClass().equals(userIndexClass)) { // 遍历该JavaBean Class 绑定的Binder ,或得一对多绑定的 position
return i;
}
}
throw new IndexOutOfBoundsException(
String.format("%s is out of your registered binders'(%s) bounds.",
userIndexClass.getName(), Arrays.toString(binders))
);
}
复制代码
其实就是,MultiTypeAdapter在渲染多类型布局的时候,根据Adapter 的数据集合拿到第i个数据的JavaBean Class,而后根据这个Classs, 拿到ViewType 。以后onCreateViewHolder方法和 onBindViewHolder方法根据这个View Type 拿到对应的Binder,而后让相应的Binder来进行绑定布局和绑定数据。
MultiType源码:github.com/drakeet/Mul…