Android框架源码解析之(六)MultiType

介绍

MultiType 可以简单,灵活的为RecyclerView实现多类型列表。

MultiType介绍:https://juejin.im/post/59702b606fb9a06ba14bc1b0

MultiType源码:https://github.com/drakeet/MultiType

使用方法

1、创建RecyclerView 的数据 Java Bean

data class TextItem(val text : String)

2、创建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
    }
}

3、使用adapter 的 register()方法绑定ItemBinder 和 对应的 Java Bean。


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()
    }
}

高级用法

1、实现一对多绑定,即一种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
    }
});

2、供以下方法进行重写

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;  // 实现 一对一绑定,一对多绑定的帮助类

映射表举例:

ViewTypeJava Bean ClassLinkerItemViewBinder
0C1L1binder_1
1C2L2binder_2_1
2C2L2binder_2_2
3C2L2binder_2_3
4C2L3binder_3_1

源码本质是,MultiTypeAdapter在渲染多类型布局的时候,根据Adapter 的数据集合拿到第i个数据的JavaBean Class,然后根据这个Classs, 拿到ViewType 。之后onCreateViewHolder方法和 onBindViewHolder方法根据这个View Type 拿到对应的Binder,然后让相应的Binder来进行绑定布局和绑定数据。

相信看到这个表之后,会对上面的话理解更清晰一些。

好了,有了前面这些铺垫,理解下面的内容会容易很多。

1、首先获取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());
}

2、调用 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,用于绑定数据。相当于下发
}

3、如何绑定,也就是如何填充上面的数据映射表

一对一绑定的实现方式:

// 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 所维护的映射表中,实现映射
  }
}

4、一对多绑定的索引是如何查找的

// 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))
  );
}

参考

MultiType源码:https://github.com/drakeet/MultiType

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值