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…