RecyclerView 滚动时回收和复用机制
共 6585字,需浏览 14分钟
·
2021-03-25 22:32
谈到RecyclerView的时候,复用机制是我们能脱口而出的优点之一。系统内置的ViewHolder避免了使用ListView时手动去创建ViewHolder的麻烦。关于何时回收View,何时复用View,我们能做到胸有成竹吗?当我们滑动一个RecyclerView时,是先回收View,再复用View?还是先复用View,再回收View呢?答案是都有可能。详情且看下面分析:
「名词解释」
「1. 回收:是指View不需要再展示在屏幕中,被回收到回收池中」
「2. 复用:本文中的复用是指调用了onCreateViewHolder或者onBindViewHolder方法」
1.滑动RV的两个场景
1.1 场景一
RV中每个Item高度都为100px,最后一个Item超出屏幕50px。RV初始状态如下图
Q1 假设向上滑动40px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
Q2 假设向上滑动60px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
Q3 假设向上滑动120px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
1.2 场景二
RV中第一个Item高度为50px,其它都为100px,最后一个Item超出屏幕95px。RV初始状态如下
Q1 假设向上滑动40px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
Q2 假设向上滑动60px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
Q3 假设向上滑动120px
请问是否有View发生回收和复用?如果有,先复用还是先回收?
从答案可以看出。回收和复用并没有固定的答案。它因场景而异。下面我们通过案例验证答案真伪。
2. DEMO验证答案
2.1 我们来验证场景一
程序运行图
程序代码
日志输出如下
❝首先进入初始状态
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 0
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 1
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 2
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 3
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 4
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 5
❞
❝点击上滑40px。打印日志不变。证明 回收和复用都没有发生
❞
❝点击上滑60px。打印日志如下。证明 没有发生回收,发生了复用
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 0
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 1
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 2
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 3
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 4
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 5
RecyclerView 场景一 onCreateViewHolder //只发生了复用
❞
RecyclerView 场景一 onBindViewHolder 6
❝点击上滑动120px。打印日志如下。证明 发生了回收和复用。先复用后回收
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 0
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 1
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 2
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 3
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 4
RecyclerView 场景一 onCreateViewHolder
RecyclerView 场景一 onBindViewHolder 5
RecyclerView 场景一 onCreateViewHolder //先复用
RecyclerView 场景一 onBindViewHolder 6
RecyclerView 场景一 发生回收 item 0 //后回收
❞
2.2 我们来验证场景二
程序运行图
程序代码
❝日志输出首先进入初始状态
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 0
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 1
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 2
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 3
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 4
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 5
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 6
❞
❝点击上滑40px。打印日志不变。证明 回收和复用都没有发生
❞
❝点击上滑60px。打印日志如下。证明 发生回收,没有发生复用
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 0
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 1
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 2
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 3
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 4
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 5
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 6
RecyclerView 场景二 发生回收 item 0 //只发生了回收
❞
❝点击上滑动120px。打印日志如下。证明 发生了回收和复用。先回收后复用
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 0
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 1
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 2
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 3
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 4
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 5
RecyclerView 场景二 onCreateViewHolder
RecyclerView 场景二 onBindViewHolder 6
RecyclerView 场景二 发生回收 item 0 //先回收
RecyclerView 场景二 onBindViewHolder 7 //再复用
❞
3. 滑动原理分析
3.1 相关参数
如图所示,介绍几个关于坐标的参数
delta:手指滑动的距离120px。
mOffset:RV最后一个子View的Bottom在屏幕坐标系的Y坐标600px。RV的下一个View(Item7)从mOffset处布局。
mScrollingOffset:RV最后一个子view的Bottom离RV Bottom的距离50px。向上滑动不超过该距离。如超过需创建新的View填充。
mVailable:delta-mScrollingOffset。可以填充View的空间。如果大于0表示有空间填充新的View
如果delta<mScrollingOffset,mScrollingOffset=delta,mVailable<0,即滑动距离小于mScrollingOffset,不要填充新的View
3.2 滑动填充和回收逻辑
滑动逻辑如下
从RecyclerView的第0个View开始遍历,直到View的Bottom>mScrollingOffset,并记录该View的下标index,回收[0,index)区间的View,index为开区间,如果index>=1,则会将[0,index)区间的View移除屏幕,并按照回收算法放入回收池。具体回收算法先按下不表。
如果mVailable>0,则从mOffset处,用新的View填充。mOffset+=新View的高度,mVailable-=新View的高度,mScrollingOffset+=新View的高度,如果mVailable<0,mScrollingOffset+=mVailable。布局完成后用步骤1的算法按需回收上面的View。
重复步骤2
将RV整体,向上移动delta或者consumed距离(一般是delta距离,但是当RecyclerView下面没有Item时会是具体消耗掉的距离)
逻辑1对应的代码如下
逻辑2填充View代码如下
3.3 分析场景一
根据此滑动逻辑,我们分析场景一中的向上滑动120px
mOffset = 600px
mScrollingOffset = 50px
mAvailable = 70px
item1高度100px
首先从第0个View遍历Bottom>50px。找到item1.bottom=100px,记录index=0。因为index<1。所以不发生回收
mAvailable>0,从Item6的底部,增加View Item7(此处发生复用逻辑)高度为100px,mOffset=700px,mAvailable=-30,mScrollingOffset=mScrollingOffset+100-30=120px。然后检查回收。首先从第0个View遍历Bottom>120px。找到item2.bottom=200px,记录index=1。回收[0,1)区间的View。即回收Item1
mAvailable=-30<0,退出填充逻辑
整体向上移动120px
我们看到先创建Item7 然后回收Item1。跟日志相符合
RecyclerView 场景一 onCreateViewHolder //先复用
RecyclerView 场景一 onBindViewHolder 6
RecyclerView 场景一 发生回收 item 0 //后回收
同样的逻辑我们也可以分析场景二中的向上滑动120px的情况。场景二会先发生回收,再发生复用。读者可以自己去求证。
4.源码分析
RV的滑动,最终会调用LayoutManager的scrollBy方法。我们使用的是LinearLayoutManager。
代码1 updateLayoutState方法,主要是计算mOffset等参数。
代码2 fill方法,根据剩余空间,填充View
代码3 offsetChildren,整体移动RV的子View
代码1,首先判断是否需要回收View
代码2,根据剩余空间,判断是否需要填充View
代码3 是具体的layout方法
代码4 当单个layout完成后判断是否需要回收View
5. 提问互动
最后为了巩固大家对知识的理解,提出一个问题,请在评论区写出你的答案吧。
「问题一 场景一的case3,向上滑动120px,120px大于第一个Item的高度100px,为何不先让Item1先回收掉呢?」
「问题二 谷歌这么设计的原因可能是什么?」
关注字节小站公众号,回复“滚动”获取答案
-- END --
推荐:
FFmpeg + Android AudioRecorder 音频录制编码
面试中经常被问到的 OpenGL ES 对象,你知道的有哪些?
FFmpeg + OpenGL ES 实现 3D 全景播放器
觉得不错,点个在看呗~