十六、RecyclerView前世今生

概述

RecyclerView简称Rv,是作为ListView和GridView的加强版出现的。目的是在有限的屏幕空间上显示大量的内容。它的最有含金量的设计是 item复用机制

使用方式

RecyclerView这个类里面有几个关键方法:

  1. setLayoutManager()
  2. setAdapter()
  3. setItemAnimator()
  4. addItemDecoration()

解释如下:

setLayoutManager

这是必选项。它的作用是设置Rv的布局管理器。常用的布局管理器有,线性(LinearLayout),表格(GridLayoutManganer),以及瀑布流(StaggeredGridLayoutManager).

setAdapter

必选项。它的作用是设置RV的数据适配器,当数据发生改变时,以通过者的身份告知RV进行列表刷新操作。

setItemAnimator

非必选。它用于设置item的动画

addItemDecoration

非必选,用于设置item的装饰器,比如分割线。

核心问题

  • rv是如何一步步将每一个itemView显示在屏幕上的。
  • 分析在滑动过程中,tv是如何通过缓存机制提升整体性能的

rv是按照自定义View的定制规则一步步开发出来的。所以我们分析的重点其实还是在 它的onMeasure,onLayout,onDraw上。

下图是 rv的测量方法:

以上代码可以看出,

  1. 优先判断rv的宽高是不是外部指定的具体数值。如果是,就不必测量子view,而是直接用具体数值指定给自身的宽高即可。
  2. 如果不是具体数值,那就委托给LayoutManager来进行测量,此时会调用dispatchLayoutStep2 测量子view。

下图是rv的布局方法;

其核心代码在 dispatchLayout 方法中:

如果在 onMeasure阶段没有调用 dispatchLayoutStep2()来测量子view,那么,在onLayout阶段会重新测量子view

测量子view的方法 dspatchLayoutStep2()码如下:

可以看到,mLayout.onLayoutChildren(mRecycler,mState)这句代码中 rv把子view的测量布局逻辑,全部交给了mLayout。 这个mLayout就是rv的关键部分之一:LayoutManger布局管理器。Rv的开发者,用这种接口解耦的方式,将 rv的布局托管给了外界传入的 布局管理器实例。 常用的实例无非三种,线性,表格,瀑布流

以线性布局管理器为例,它的源代码如下:

  1. 在onLayoutChildren 方法中,用 fill方法完成子view的测量布局工作
  2. 在fill方法中,用while循环调用 layoutChunk来判断是否有足够空间来放置完整的子view。 layoutChunk方法中是子view测量布局的核心实现。
  3. 每循环一次,都要重新计算 剩余空间。

layoutChunk方法,每执行一次,就填充一个子view到rv中源代码如下:

  1. layoutState是子view的缓存,每次next方法都会返回一个子view,然后调用addView或者addDisappearView将子view添加到rv中
  2. 测量被添加进去的子view的宽高间距
  3. 根据宽高和间距,最终确定子view的显示位置

测量和布局都完成之后,就轮到了绘制了。

onDraw方法中,只是将装饰 decoration逐个绘制出来。 所有的子view,则是通过android的渲染递归机制,逐个执行子view自己的onDraw显示在屏幕上。

小结1

Rv显示所有的子view会经历 测量布局绘制3个过程,但是它将测量和布局,都委托给了 LayoutManager 最后的装饰才是自己的onDraw去绘制的。

根据传入具体类型的LayoutMamager实例,rv能表现出不同的布局方式。这个叫做 策略模式。

缓存复用原理

缓存复用机制是 rv性能的保证。核心代码在下面的Recycler中。主要用来缓存屏幕内的viewholder以及部分屏幕外的viewHolder。

实际上Rv的缓存分为4个等级,之所以分为4个等级,其实是对应了4种不同的场景:

  1. 屏幕内的缓存 mAttachScrap 和 mChangedScrap

为什么屏幕内的viewHolder要缓存起来呢? 这是因为 rv在收到adapter的刷新通知之后,会对所有的子view进行全部刷新,但是,不分青红皂白,一律清空重新创建是不合理的。实际上,可能有相当数量的子view可以在屏幕内复用,仅做数据的刷新,viewHolder不进行销毁和重建。 一句话:屏幕内缓存机制,是为了应对列表刷新时,屏幕内的子view无需销毁重建的情况

  1. 刚移除到屏幕外的缓存 mCachedViews

它的默认缓存为2,且不可修改。 如果 屏幕滑动时,有些子view滑动到了屏幕外,那么,它将会进入到 mCachedViews,而由于容量有限,这些view会遵循先进先出的原则。

  1. viewCachedExtension

这是RV预留给 开发者的一个抽象类,支持我们编写自己的缓存机制,只不过不建议这么做。所以忽略它。

  1. RecycledViewPool

它也是用来缓存屏幕外的viewHolder,由于 mCachedViews 容量只有2,那些从 mCachedViews 中溢出来的viewHolder就会进入到 RecycledViewPool,同时,它的内部数据会进行清理,变成干净的ViewHolder. 所以,从RecycledViewPool 中取出的viewholder一定会重新执行 onBindViewHolder .

容易忽略的是,多个Rv之间可以共享 RecycledViewPool,这种特性经常用在 多tab界面的优化中。多个tab都有类似的rv数据。

注意:RecycledViewPool 是根据ViewType来获取viewHolder,每个viewType最大缓存数量为5.

Rv是如何从缓存中获取viewHolder的

还记得 layoutState.next() 方法么,它用来从缓存中获取viewholder。

这个方法,其实就是从这四级缓存中逐个查找,如果找到最后都没找到,那么就执行adatper中的 createViewHolder

viewHolder是何时进入到缓存的

首先,是第一次执行 setLayoutManager -> setAdapter 时 ,由于缓存为空,所以所有的viewHolder都是 createViewHolder创建出来的。

接下来,如果发生了 数据变更并且通知了adapter。那么,所有屏幕内的viewHolder被填充到一级(屏幕内)缓存。

这个过程之后,一级缓存中就有了屏幕内的viewHolder缓存。此时,createViewHolder不会执行,由于数据需要被填充,所以,rv会执行 onBindViewHodlder,将旧的viewHolder结合新的数据,展示在屏幕上。

总结

RecyclerView 核心点有2:

  1. LayoutManager
  2. Recycler

左右护法,一个解耦了 布局方式,一个利用缓存保证了性能。

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端