introduce

MultiType is a simple and flexible way to implement MultiType lists for RecyclerView.

Introduction to MultiType: juejin.cn/post/684490…

MultiType source: github.com/drakeet/Mul…

Method of use

1, create RecyclerView data Java Bean

data class TextItem(val text : String)
Copy the code

Create TextItemViewBinder from ItemViewBinder. Similar to the 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
    }
}
Copy the code

3. Bind ItemBinder to the corresponding Java Bean using Adapter register().


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 and TextItemViewBinder bind adapter!! .register(ImageItem::class.java, ImageItemViewBinder()) // ImageItem and ImageItemViewBinder bind adapter!! .register(RichItem::class.java, RichItemViewBinder()) // RichItemViewBinder binding recyclerView? .adapter = adapter items!! .add(TextItem("world")) items!! .add(ImageItem(R.mipmap.ic_launcher)) items!! .add(RichItem("Little Lord Eldrin segow.", R.drawable.img_11)) adapter? .items = items as Items adapter? .notifyDataSetChanged() } }Copy the code

Advanced usage

Implement one-to-many binding, that is, one Java Bean binding to one ViewHolder.

adapter.register(Data.class).to(
    new DataType1ViewBinder(),
    new DataType2ViewBinder()
).withClassLinker((position, data) -> {
    if (data.type == Data.TYPE_2) {
        returnDataType2ViewBinder.class; // If data.type is data.type_2, use DataType2ViewBinder}else {
        returnDataType1ViewBinder.class; // Otherwise, use DataType1ViewBinder}});Copy the code

2, for the following method to rewrite

Protected long getItemId(@nonNULL T item) protected void onViewRecycled(@nonNULL VH holder) // ViewHolder is called when it is recycled Protected Boolean onFailedToRecycleView(@nonNULL VH holder) // Call protected void when ViewHolder fails to create OnViewAttachedToWindow (@nonNULL VH holder) // Call protected void onViewDetachedFromWindow(@nonNULL VH holder) when Attach Detach the callCopy the code

Source code analysis

Note: Source code analysis is for the 3.x branch

First to a more intuitive picture, otherwise look at the source is very meng force:

The JavaBean Class is used to obtain the Position of the ViewType.

When a MultiTypeAdapter renders a multitype layout, it retrieves the i-th JavaBean Class according to the Adapter’s data set, and retrieves the ViewType according to the Classs. The onCreateViewHolder and onBindViewHolder methods then get the corresponding Binder according to the View Type and let the corresponding Binder do the binding layout and binding data.

The framework maintains a mapping table, which corresponds to the quadruple binding of ViewType,JavaBean Class, Linker and ItemViewBinder.

Internally mapped data structure:

private final @NonNull List<Class<? >> classes; // Java Bean Class private final @NonNull List<ItemViewBinder<? ,? >> binders; ViewHolder private final @nonnull List<Linker<? >> linkers; // Implement one-to-one binding, one-to-many binding help classCopy the code

Mapping table examples:

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

When a MultiTypeAdapter renders a multitype layout, it retrieves the i-th JavaBean Class according to the Adapter’s data set, and retrieves the ViewType according to the Classs. The onCreateViewHolder and onBindViewHolder methods then get the corresponding Binder according to the View Type and let the corresponding Binder do the binding layout and binding data.

I believe that after seeing this table, you will understand the above words a little more clearly.

Ok, so with that in mind, it’s a lot easier to understand what’s going on.

1. First get the Type of the ViewHolder, then get the ViewHolder

// MultiTypeAdapter.java @Override public final int getItemViewType(int position) { Object item = items.get(position); // Get the data from position in the setreturnindexInTypesOf(position, item); ViewType} int indexInTypesOf(int position, @nonNULL Object item) throws BinderNotFoundException {int index =typePool.firstIndexOf(item.getClass()); // Get the first occurrence position of the Class corresponding to item in the mapping tableif(index ! = -1) { @SuppressWarnings("unchecked")
    Linker<Object> linker = (Linker<Object>) typePool.getLinker(index);
    returnindex + linker.index(position, item); // Linker. index(position, itemtypeIf there is no one-to-many binding, linker.index(position, item) is 0. This is the position where the class first appears in the mapping table.typePlastic for, The first occurrence of the class in the mapping table + one-to-many registration of the corresponding index to achieve position and the corresponding ItemViewType mapping */} throw New BinderNotFoundException(item.getClass()); }Copy the code

2. Call onCreaterViewHolder and onBindViewHolder to bind the layout and populate data.

// MultiTypeAdapter.java @Override public final ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); ItemViewBinder<? ,? > binder =typePool.getItemViewBinder(indexViewType); ItemViewBinder = ItemViewBinder = ItemViewBinderreturnbinder.onCreateViewHolder(inflater, parent); // Call onCreateViewHolder of ItemViewBinder for binding layout. } @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); // ItemViewBinder binder.onBindViewHolder(holder, item, payloads); // Call onBindViewHolder of ItemViewBinder to bind data. Equivalent to delivering}Copy the code

3. How to bind, i.e., how to populate the data mapping table above

One-to-one binding is implemented as follows:

// 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); } // Add a row mapping by inserting a row into the mapping table.Copy the code

Implementation of one-to-many binding:

Let’s first look at the one-to-many binding:

Register (data.class).to(new DataType1ViewBinder(), new DataType2ViewBinder() ).withClassLinker((position, data) -> {if (data.type == Data.TYPE_2) {
        return DataType2ViewBinder.class;
    } else {
        returnDataType1ViewBinder.class; }});Copy the code

Look at the withClassLinker () method.

// 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); // Insert Class, Binder, Linker into the MultiTypePool mapping table}}Copy the code

4. How are indexes for one-to-many bindings found

// ClassLinkerWrapper.java @Override public int index(int position, @NonNull T t) { Class<? > userIndexClass = classLinker.index(position, t); // This method is defined when the user implements one-to-many binding.for (int i = 0; i < binders.length; i++) {
    ifBinders [I].getClass().equals(userIndexClass)) {binders[I].getClass().equals(userIndexClass)) {binders[I].getClass().equals(userIndexClass)) {binders[I].getClass()return i;
    }
  }
  throw new IndexOutOfBoundsException(
      String.format("%s is out of your registered binders'(%s) bounds.",
          userIndexClass.getName(), Arrays.toString(binders))
  );
}
Copy the code

When a MultiTypeAdapter renders a multitype layout, it retrieves the i-th JavaBean Class from the Adapter’s data set, and retrieves the ViewType from the Classs. The onCreateViewHolder and onBindViewHolder methods then get the corresponding Binder according to the View Type and let the corresponding Binder do the binding layout and binding data.

reference

MultiType source: github.com/drakeet/Mul…