RecyclerView多類型列表實現—— MultiType分析

介紹

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…

相關文章
相關標籤/搜索