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…

相关文章
相关标签/搜索