文章目录
一、概述
我们知道 RecyclerView 其实是继承 ViewGroup 实现的,也就是说它的最根本的功能是可以往 RecyclerView 中添加子 View。如往 LinearLayout 中添加子 View,子 View 就会横向或纵向展开排列。以 LinearLayout 纵向排列为例,当子 View 过多时,超出屏幕部分的子 View 就显示不出来,这时引入一种
滑动机制
来确保所有子 View 都可以展示 (通过 Scroller/ OverScroller 类辅助实现滑动效果),类似 ScrollerView。当子 View 数量很多时,又会产生另一个新问题 (内存占用大:因为所有子 View 需要同时存在)。但是这种场景下有个特点,即屏幕展示的子 View 有限,大部分子 View 都不可见。所以新增了View 的复用机制
,即通过回收不可见的子 View 并在展示新 View 时进行复用来实现优化。这便是 ListView、RecyclerView 等存在的意义。
版本: Android SDK 29
关联文章:
- 《View系列 (一) — Android 坐标系》
- 《View系列 (二) — MeasureSpec 详解》
- 《View系列 (三) — Measure 流程详解》
- 《View系列 (四) — Layout 流程详解》
- 《View系列 (五) — Draw 流程详解》
- 《Window系列 (一) — WindowManager 详解》
官方提供 RecyclerView 来替换 ListView,想必有其过人之处,因此本文我们着重来分析一下 RecyclerView。
RecyclerView 可以分为以下几个模块:
- LayoutManager:负责 RecyclerView 中,控制 item 的布局方向。
- Recycler:负责View的缓存。
- RecyclerView.Adapter:适配器模式,为 RecyclerView 承载数据。
- ItemDecoration:为 RecyclerView 添加分割线。
- ItemAnimator:控制 RecyclerView 中 item 的动画。
从上面我们可以看出相比于ListView,RecyclerView 是一个高度解耦的控件(高扩展性),所以也就更复杂。但无论 RecyclerView 多么复杂,本质上还是一个 View,所以接下来我们按照 View 绘制的三大流程来分析。
二、RecyclerView 绘制流程分析
View 的三大绘制流程: 《Window系列 (一) — WindowManager 详解》
2.1 onMeasure
首先我们来看看 measure 过程,最终会触发 RecyclerView.onMeasure() 方法。具体触发流程可以参考:《View系列 (三) — Measure 流程详解》
// RecyclerView.class
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// Condition1
return;
}
if (mLayout.mAutoMeasure) {
// Condition2
} else {
// Condition3
}
}
从上图可知,onMeasure 方法的逻辑可以分为3种情况。
- Condition1:mLayout 即 LayoutManager 的对象。当 RecyclerView 的 LayoutManager 为空时,RecyclerView 不能显示任何的数据。
- Condition2:LayoutManager 开启了自动测量时 (默认)。在这种情况下,有可能会测量两次。
- Condition3:没有开启自动测量的情况(自定义测量)。
Condition1:LayoutManager 不存在
第一种场景:当没有设置 mLayout 时,屏幕不会展示任何数据。
// RecyclerView.class
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(), getMinimumWidth());
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(), getMinimumHeight());
// 给mMeasuredWidth和mMeasuredHeight属性赋值,这也就是为什么要执行完onMeasure阶段后这两个数据才有值的原因。
setMeasuredDimension(width, height);
}
chooseSize()
方法通过从 RecyclerView 的 MeasureSpec
获取 mode 值来返回不同的值。具体的模式可以参考:
《View系列 (二) — MeasureSpec 详解》
// RecyclerView.class
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
到此,第一种情况的 onMeasure 就分析完了。
而 onLayout()
流程会因为 mLayout
为 Null 被中断流程,这也就是 RecyclerView 在没有设置 LayoutManager 时会显示空白的原因。因为 RecyclerView 中子 View 的 layout 过程都委托给 LayoutManager 中执行。
// RecyclerView.class
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
// mLayout 不存在时,会中断onLayout流程。
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
}
小结:
当 mLayout 为 Null 时,onMeasure 流程会执行默认的尺寸测量流程,onLayout 流程没有执行任何逻辑。
Condition2:LayoutManager 自动测量
在开始分析之前,我们先来了解以下3个方法和 mState.mLayoutStep 变量几种状态的含义:
3个方法的含义:
方法 | 含义 |
---|---|
dispatchLayoutStep1() | 进行预布局,主要是记录数据更新时需要进行的动画所需的信息有关。 |
dispatchLayoutStep2() | 主要是执行 子View 的测量布局工作。 |
dispatchLayoutStep3() | 主要是用来实际执行动画。 |
mState.mLayoutStep 几种常量的含义:
状态 | 含义 |
---|---|
State.STEP_START | 默认值,还未调用 dispatchLayoutStep1() 方法 |
State.STEP_LAYOUT | 表示当前处于 layout 阶段,还未调用 dispatchLayoutStep2() 方法 |
State.STEP_ANIMATIONS | 表示当前处于动画执行阶段,还未调用 dispatchLayoutStep3() 方法 |
mState.mLayoutStep 状态在3个方法中流转关系如下图所示:
- mState.mLayoutStep 初始状态为 State.STEP_START,表示接下去要执行 dispatchLayoutStep1() 方法。
- 当执行 dispatchLayoutStep1() 方法后会将状态设置成 State.STEP_LAYOUT,表示接下去要执行 dispatchLayoutStep2() 方法。
- 当执行 dispatchLayoutStep2() 方法后会将状态设置成 State.STEP_ANIMATIONS,表示接下去要执行 dispatchLayoutStep3() 方法。
- 当执行 dispatchLayoutStep3() 方法后会将状态重置成 State.STEP_START (
注意
),表示本次的 layout 流程已经结束,为下次的 layout 流程做准备,所以需要将mState.mLayoutStep
状态重置。
为什么要设置 mState.mLayoutStep 状态与 dispatchLayoutStep_n() 的3个方法做关联呢?
这与 onMeasure 阶段是否执行自动测量逻辑有关。由于存在下面两种执行情况,因此需要通过一个变量 (
mState.mLayoutStep
) 的状态流转来判断执行的逻辑。
- 当 onMeasure 阶段执行 Condition2 的自动测量时,会先执行 dispatchLayoutStep1()、dispatchLayoutStep2() 方法,在 onLayout 阶段会执行 dispatchLayoutStep3() 方法。
- 当 onMeasure 阶段执行 Condition3 自定义测量时, 只有到了 onLayout 阶段才会执行 dispatchLayoutStep1()、dispatchLayoutStep2() 、dispatchLayoutStep3() 方法。
接下来我们开始分析 onMeasure 过程的 Condition2 流程。
// RecyclerView.class
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// Condition1
return;
}
if (mLayout.mAutoMeasure) {
// Condition2
// 1.测量当前RecyclerView的尺寸信息,如果尺寸可以确定,则委托LayoutManager执行onMeasure()过程。
final int widthMode = MeasureSpec.getMode(widthSpec);