当前位置: 首页 > news >正文

聊聊RecyclerView新出的ConcatAdapter如何使用

1.前言

2021年4月7日Android团队正式发布了RecyclerView 1.2.0版本。相对于1.1.0版本,它有两个主要的变化:

  1. 增加了ConcatAdapter:这个Adapter方便地让我们在一个RecyclerView中连接多个Adapters。

  2. 支持延迟恢复状态:RecyclerView现在支持当内容加载出来后恢复状态。

本文将结合ConcatAdapter的简单使用,由浅入深地讲解ConcatAdapter的高级使用。

2.简单使用

实现上面是文本列表,下面是按钮列表的效果,如图:

运行效果

2.1 不使用ConcatAdapter实现

在RecyclerView 1.2.0之前,我们可以通过Adapte的getItemViewType方法,设置文本和按钮两种类型。来完成上述效果。伪代码如下,通过TEXT_TYPE和BUTTON_TYPE两种类型,创建不同的视图。

2.2 使用ConcatAdapter实现

使用ConcatAdapter实现该效果。只需要创建TextAdapter处理文本列表,创建ButtonAdapter处理按钮列表。通过ConcatAdapter将它们串联起来。代码如下:

2.3 优势和劣势

使用ConcatAdapter的优势是Adapter可重用性高,更专注在业务上,不必考虑各种不同ItemType的场景,耦合度低。劣势是,ConcatAdapter不支持不同ItemType交叉出现的场景。

3. 高级进阶

以上就是ConcatAdapter简单使用的全部教程。但是如果你认为ConcatAdapter就这么简单那你就大错特错了。让我们深入源码,玩点更高级的特性吧。

3.1 Config类

我们看到ConcatAdapter有如下构造函数。我们注意到Config类是ConcatAdapter的静态内部类。

public ConcatAdapter(Adapter<? extends ViewHolder>... adapters) {
    this(Config.DEFAULT, adapters);
}

public ConcatAdapter(Config config, Adapter<? extends ViewHolder>... adapters) {
    this(config, Arrays.asList(adapters));
}

Config构造函数如下:

Config(boolean isolateViewTypes, StableIdMode stableIdMode) {
    this.isolateViewTypes = isolateViewTypes;
    this.stableIdMode = stableIdMode;
}
public static final Config DEFAULT = new Config(true, NO_STABLE_IDS);

我们注意到默认的Config,isolateViewTypes值为true。

3.2 isolateViewTypes含义

要讲清楚isolateViewTypes的含义,那么必须先明白viewType与缓存的关系。我们都知道RecyclerViewPool中是根据viewType缓存ViewHolder的。如果viewType相同,那么它对应的缓存池相同。

RecyclerViewPool缓存示意图如下。每个不同的viewType都有一个属于它自己的缓存。

isolateViewTypes为true。表示ConcatAdapter中的子Adapter的viewType,会被ConcatAdapter隔离开。即使两个子Adapter的中元素的viewType相同,ConcatAdapter会将它们分隔成不同的viewType。从缓存的角度看,即使两个相同的Adapter,它们也无法共用一个缓存池。

isolateViewTypes为false。表示如果viewType相同,那么它们将共用一个缓存池。

3.1 不共用缓存

假设有ConcatAdapter,连接了RedAdapter、OrangeAdapter、BlueAdapter、RedAdapter。使用默认Config。isolateViewTypes为true,我们看到RedAdapter的ViewType默认返回1。但是从ConcatAdapter的角度。两个RedAdapter的viewType分别为0和3。

RedAdapter redAdapter1 = xxx;
OrangeAdapter orangerAdapter = xxx;
BlueAdapter blueAdapter = xxx;
RedAdapter redAdapter2 = xxx;

ConcatAdapter concatenated = new ConcatAdapter(redAdapter1, orangerAdapter,blueAdapter,redAdapter2);
recyclerView.setAdapter(concatenated);

3.2 共用缓存

RedAdapter redAdapter1 = xxx;
OrangeAdapter orangerAdapter = xxx;
BlueAdapter blueAdapter = xxx;
RedAdapter redAdapter2 = xxx;

//isolateViewTypes为false
ConcatAdapter.Config config = ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build()

ConcatAdapter concatenated = new ConcatAdapter(config, redAdapter1, orangerAdapter,blueAdapter,redAdapter2);
recyclerView.setAdapter(concatenated);

ConcatAdapter的itemType 和子Adapter的itemType一致。

4. 爬坑

4.1 坑一

子Adapter的getItemViewType返回默认值。ConcatAdapter isolateViewType设置为false。

TextAdapter和ButtonAdapter和上文一样。

class ConcatAdapterDemoActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_concat_adapter_demo)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
        recyclerView.layoutManager = LinearLayoutManager(this)
        val config = ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build()
        recyclerView.adapter = ConcatAdapter(
            config,
            TextAdapter(),
            ButtonAdapter()
        )
    }
}

4.2 坑二

ConcatAdapter连接多个TextAdapter。isolateViewType设置为true。发现滚动到第二个TextAdapter位置时,又创建了新的TextView。这种情况,相同viewType需要共用缓存。将isolateViewType设置为false。

recyclerView.adapter = ConcatAdapter(
      config,
      TextAdapter(),
      ButtonAdapter(),
      TextAdapter()
)

5. 原理

  1. ConcatAdapter.getItemViewType()

//ConcatAdapter.java
@Override
public int getItemViewType(int position) {
    return mController.getItemViewType(position);
}
  1. ConcatAdapterController.getItemViewType(int globalPosition)

//ConcatAdapterController.java
public int getItemViewType(int globalPosition) {
      //根据globalPosition找到对应的子Adapter的Wrapper
      WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
      int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
      releaseWrapperAndLocalPosition(wrapperAndPos);
      return itemViewType;
}
  1. NestedAdapterWrapper.getItemViewType(int localPosition)

int getItemViewType(int localPosition) {
    return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
}
  1. IsolatedViewTypeStorage$WrapperViewTypeLookup.localToGlobal()

@Override
public int localToGlobal(int localType) {
    int index = mLocalToGlobalMapping.indexOfKey(localType);
    if (index > -1) {
        return mLocalToGlobalMapping.valueAt(index);
    }
    // get a new key.
    int globalType = obtainViewType(mWrapper);
    mLocalToGlobalMapping.put(localType, globalType);
    mGlobalToLocalMapping.put(globalType, localType);
    return globalType;
}
  1. IsolatedViewTypeStorage$WrapperViewTypeLookup。从代码可以看出在不共享缓存池的情况下。「子Adapter的viewType会从0递增对应」

int mNextViewType = 0;
int obtainViewType(NestedAdapterWrapper wrapper) {
    int nextId = mNextViewType++;
    mGlobalTypeToWrapper.put(nextId, wrapper);
    return nextId;
}
  1. isolateViewTypes为true的情况下。会使用SharedIdRangeViewTypeStorage$WrapperViewTypeLookup。我们看到 localType和globalType相等。

@Override
public int localToGlobal(int localType) {
    // register it first
    List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.get(
            localType);
    if (wrappers == null) {
        wrappers = new ArrayList<>();
        mGlobalTypeToWrapper.put(localType, wrappers);
    }
    if (!wrappers.contains(mWrapper)) {
        wrappers.add(mWrapper);
    }
    return localType;
}

@Override
public int globalToLocal(int globalType) {
    return globalType;
}

6. 最后

成文仓促,如果有表述不清晰的地方,欢迎留言探讨。

相关文章:

  • CPU 进化论:复杂指令集 CISC
  • 一些Camera相关概念整理
  • 速看!!!openEuler Developer Day 2021 抢票倒计时
  • FFmpeg源码世界:命令篇
  • 珍惜还在搞音视频输出的大佬们~~
  • 市场从增量到存量博弈,对技术要求也更精细了~
  • 揭秘版权保护下的视频隐形水印算法(上篇)
  • 字节终面:CPU 是如何读写内存的?
  • 声网下一代视频引擎架构探索与实践
  • 阿里终面:为什么SSD不能当做内存用?
  • 首发|语音信号处理免费体验营
  • 摩天大楼如何靠一颗铁球防风抗震?
  • 铁打的 Kotlin ,从来没让我失望
  • CPU 核数与线程数有什么关系?
  • Android 开发垂直领域的大佬
  • 【5+】跨webview多页面 触发事件(二)
  • 【刷算法】求1+2+3+...+n
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • 10个最佳ES6特性 ES7与ES8的特性
  • Apache Spark Streaming 使用实例
  • Django 博客开发教程 8 - 博客文章详情页
  • es6
  • Fastjson的基本使用方法大全
  • HTTP中GET与POST的区别 99%的错误认识
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • JSDuck 与 AngularJS 融合技巧
  • Kibana配置logstash,报表一体化
  • LintCode 31. partitionArray 数组划分
  • Octave 入门
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • spring学习第二天
  • 来,膜拜下android roadmap,强大的执行力
  • 码农张的Bug人生 - 初来乍到
  • 面试总结JavaScript篇
  • 用简单代码看卷积组块发展
  • Java数据解析之JSON
  • $.each()与$(selector).each()
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (论文阅读30/100)Convolutional Pose Machines
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (南京观海微电子)——COF介绍
  • (转)setTimeout 和 setInterval 的区别
  • (转)创业家杂志:UCWEB天使第一步
  • (转)拼包函数及网络封包的异常处理(含代码)
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .htaccess配置常用技巧
  • .net 受管制代码
  • .net与java建立WebService再互相调用
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [20180129]bash显示path环境变量.txt
  • [AIGC] 开源流程引擎哪个好,如何选型?