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

2023-10-26

介绍

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

映射表举例:

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来进行绑定布局和绑定数据。

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android框架源码解析之(六)MultiType 的相关文章

  • 如何为ScrollView放置固定图像背景?

    我应该想要滚动视图滚动 而不是背景中的图像 将图像添加到滚动视图框架之前的视图层次结构的较高位置
  • 如何在 Android 中保存相机的临时照片?

    在尝试从相机拍照并将其保存到应用程序的缓存文件夹中时 我没有得到任何可见的结果 应用程序不会崩溃 但在 LogCat 上 当我尝试将 ImageView src 字段设置为刚刚获取的文件的 URI 时 我收到此消息 09 17 14 03
  • 如何重试已消耗的 Observable?

    我正在尝试重新执行失败的已定义可观察对象 一起使用 Retrofit2 和 RxJava2 我想在单击按钮时重试特定请求及其订阅和行为 那可能吗 service excecuteLoginService url tokenModel Ret
  • 无法获取log.d或输出Robolectrict + gradle

    有没有人能够将 System out 或 Log d 跟踪从 robolectric 测试输出到 gradle 控制台 我在用Robolectric Gradle 测试插件 https github com robolectric robo
  • 计数物体和更好的填充孔的方法

    我是 OpenCV 新手 正在尝试计算物体的数量在图像中 我在使用 MATLAB 图像处理工具箱之前已经完成了此操作 并在 OpenCV Android 中也采用了相同的方法 第一步是将图像转换为灰度 然后对其进行阈值计算 然后计算斑点的数
  • 当文本输入聚焦在 React Native for Android 的底部工作表上时,视图移出屏幕

    我正在使用图书馆 https github com osdnk react native reanimated bottom sheet https github com osdnk react native reanimated bott
  • Android 模拟器插件无法初始化后端 EGL 显示

    我在 Cloudbees 上设置了 Jenkins 作业 并且可以在那里成功签出并编译我的 Android 项目 现在我想在 android 模拟器中运行一些 JUnit 测试并添加 Android 模拟器插件 我将 显示模拟器窗口 选项设
  • Android 中 Kotlin 协程的正确使用方式

    我正在尝试使用异步更新适配器内的列表 我可以看到有太多的样板 这是使用 Kotlin 协程的正确方法吗 这个可以进一步优化吗 fun loadListOfMediaInAsync async CommonPool try Long runn
  • 无法访问 com.google.android.gms.internal.zzbfm 的 zzbfm 类文件未找到

    我正在将我的 Android 应用程序项目从GCM to FCM 为此 我使用 Android Studio 中的 Firebase 助手工具 并遵循 Google 开发人员指南中的说明 一切都很顺利 并将我的应用程序代码更改为FCM根据助
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • 发布android后更改应用内购买项目的价格

    在 Google Play 上发布后 是否可以更改应用内购买商品的价格 我假设该应用程序也已发布 完整的在线文档位于http developer android com http developer android com也http sup
  • 在 android DatePickerDialog 中将语言设置为法语

    有什么办法可以让日期显示在DatePickerDialog用法语 我已经搜索过这个但没有找到结果 这是我的代码 Calendar c Calendar getInstance picker new DatePickerDialog Paym
  • Android Studio 0.4.3 Eclipse项目没有gradle

    在此版本之前 在 Android Studio 中按原样打开 Eclipse 项目似乎很容易 无需任何转换 我更喜欢 Android Studio 环境 但我正在开发一个使用 eclipse 作为主要 IDE 的项目 我不想只为这个项目下载
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 在activity_main.xml中注释

    我是安卓新手 据我所知 XML 中的注释与 HTML 中的注释相同 使用 形式 我想在 Android 项目的 Activity main xml 配置文件中写一些注释 但它给了我错误 值得注意的是 我使用的是 Eclipse 但目前 我直
  • Android:膨胀布局时出现 StackOverFlowError 和 InvokingTargetException

    首先 对不起我的英语 我在膨胀布局时有一个问题 我有一个自定义视图 从 LinearLayout 扩展而来 称为按钮帮助 我在名为的布局上使用该视图加载活动 我的以下代码在所有设备和模拟器上都能完美运行 但具有 QVGA 屏幕 例如 Sam
  • Crashlytics 出现 Android Studio 构建错误

    我正在尝试将 CrashLytics 与 Android Studio 和 gradle 一起使用 但出现一个令人困惑的错误 java lang NoSuchMethodError 我的 build gradle 是 buildscript

随机推荐

  • 深度卷积神经网络在目标检测中的进展

    作者 travelsea 链接 https zhuanlan zhihu com p 22045213 来源 知乎 近些年来 深度卷积神经网络 DCNN 在图像分类和识别上取得了很显著的提高 回顾从2014到2016这两年多的时间 先后涌现
  • 【无标题】

    1 直达软件 由直达国际的子公司上海直达软件有限公司出品 提供期货交易全套解决方案的专业软件 全球化交易平台 结算系统 风险管理平台及程序化交易等产品 为公司客户提供稳定 快速的交易系统 2 易盛极星 易盛极星 是由郑州商品交易所的全资子公
  • BigDecimal 如何在一个区间的比较

    BigDecimal 如何在一个区间的比较 背景 由于BigDecimal 的精度比较高 在计算的时候有时候存入数据库的时候会进行四舍五入 会对后面的结果判断存在误差 所以比较的结果会在一个区间的 public static boolean
  • vim编辑文件使用案例

    1 前置知识点了解 操作流程 编辑文件指令 vim 文件名 点击键盘 i 键 模式由默认的 命令模式 变成 插入模式 编辑文件内容 完成后点击键盘 Esc 键 模式由 插入模式 变成 命令模式 同时点击键盘 shift 键 模式由 命令模式
  • python接口自动化(四)--接口测试工具介绍(详解)

    简介 工欲善其事必先利其器 通过前边几篇文章的介绍 大家大致对接口有了进一步的认识 那么接下来让我们看看接口测试的工具有哪些 目前 市场上有很多支持接口测试的工具 利用工具进行接口测试 能够提供测试效率 例如 假 入让你一天完成100个接口
  • docker-compose常见问题

    一 新版本网络桥接 问题 docker errors InvalidArgument host network mode is incompatible with port 1 24 0以前可以network mode host和ports
  • Python List 按照多个关键字排序

    最近刷刷题遇到的 发现还没有一模一样的答案 自己做个解答 以列表有两列为例 我们需要按照两列排序 可以利用sorted和lambda组合 l a 2 c 1 d 4 b 2 sorted l key lambda x x 1 x 0 rev
  • 小程序实现h5页面的微信支付php,微信小程序webview组件交互,内联h5页面并网页实现微信支付实现解析...

    前言 小程序支持webview以后 我们开发的好多h5页面 就可以直接在小程序里使用了 比如我们开发的微信商城 文章详情页 商品详情页 就可以开发一套 多处使用了 我们今天来讲一讲 在小程序的webview里实现微信支付功能 因为微信不允许
  • 面向对象、面向过程的思考

    把问题和答案记下来 过段时间再自己回答 几 次 2014年04月30号 师傅 思考一个问题 面向对象编程 和 面向过程编程 两类型的语言又什么区别 我 面向对象编程 最大的感觉就是分工的明确 从小的方面来说 就是把程序里面的一个个对象 类
  • 解剖上海交大女生无耻言论:只嫁外国人?

    上海交大MM的不嫁中国男人自白书原文 我是上海交大的一名女大学生 我第一次发帖子 写的不好 请大家表笑偶 每个人都有人生理想 有的人想成为英雄 有人的想成为富翁 有的人渴望成为领袖 我呢 人生最大的愿望就是嫁给一个西方男人 有人会说我很庸俗
  • 重定向与请求转发,以及它们之间的区别

    重定向 在某些情况下 针对客户端的请求 一个Servlet类可能无法完成全部工作 这时 可以使用请求重定向来完成 所谓请求重定向 是指Web服务器接收到客户端的请求后 可能由于某些条件限制 不能访问当前请求URL所指向的Web资源 而是指定
  • 高效管理之团队梯度建设

    经常听人讲 我们要建设高效的团队 如何提高团队的执行效率等等 空谈效率没有意义 这篇文章结合作者自身的经历 谈谈梯度团队是什么样子的 为什么一个有梯度的团队是高效的 以及在管理中如何建设这样的团队 梯度团队介绍 下图是我经历的一家中型互联网
  • Linux下VIM编辑器的详细使用

    1 VI编辑器的启动与退出 vi file1 新建一个文本文件为file1 q 在末行模式下退出 2 文本的操作 e file1 在当前文件下编辑新的文件 r etc passwd 实现文件的读入功能 wq 保持并退出 q 强行退出 3 光
  • Qt的容器类——QList

    定义 QList lt T gt 以数组列表形式实现 在其前后添加数组非常快 注意Qt中有很多函数返回了QList类型 要遍历这些返回的容器 必须先复制再遍历 因为Qt使用了隐式复制 所以开销并不大 当一个迭代器在操作一个容器时 不要复制这
  • 编码与解码

    什么是编码与解码 电脑是由电路板组成 电路板里面集成了无数的电阻和电容 交流电经过电容的时候 电压比较低 记为低电平 用0表示 交流电流过电阻的时候 电压比较高 记为高电平 用1来表示 所以每一个1 和0 在计算机中被称为 位 也就是bit
  • 微信开发ios上传图片到服务器,微信开发上传图片ios与安卓兼容问题

    首先检查一下引入js的版本 用最新的 直接上代码 kin img click function var ua navigator userAgent toLowerCase if ua match iphone i micromesseng
  • 使用 spring.profiles.active 及 @profile 注解 动态化配置内部及外部配置

    https blog csdn net swordsnapliu article details 78540902 引言 使用 spring profiles active 参数 搭配 Profile注解 可以实现不同环境下 开发 测试 生
  • I2C局部架构

    与子系统集成时候 APB Slave接口的master为AHB2APB Bridge I2C接口连接系统I2C总线 工作时候 先使用APB接口配置寄存器 决定I2C的工作模式 随后向指令寄存器写指令 使I2C进行数据传输操作 基本功能 可以
  • 最详细 openEuler 安装教程

    随着Centos的逐步转移 Centos8在2021年12月31号将停止维护 随而Centos7也将于2024年逐步停止维护 我们可以转向openEuler 也就是华为服务器操作系统 EulerOS 开源后命名为 openEuler 面向企
  • Android框架源码解析之(六)MultiType

    介绍 MultiType 可以简单 灵活的为RecyclerView实现多类型列表 MultiType介绍 https juejin im post 59702b606fb9a06ba14bc1b0 MultiType源码 https gi