RecyclerView(一) — 绘制流程分析

一、概述

我们知道 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
关联文章:

  1. 《View系列 (一) — Android 坐标系》
  2. 《View系列 (二) — MeasureSpec 详解》
  3. 《View系列 (三) — Measure 流程详解》
  4. 《View系列 (四) — Layout 流程详解》
  5. 《View系列 (五) — Draw 流程详解》
  6. 《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个方法中流转关系如下图所示:

  1. mState.mLayoutStep 初始状态为 State.STEP_START,表示接下去要执行 dispatchLayoutStep1() 方法。
  2. 当执行 dispatchLayoutStep1() 方法后会将状态设置成 State.STEP_LAYOUT,表示接下去要执行 dispatchLayoutStep2() 方法。
  3. 当执行 dispatchLayoutStep2() 方法后会将状态设置成 State.STEP_ANIMATIONS,表示接下去要执行 dispatchLayoutStep3() 方法。
  4. 当执行 dispatchLayoutStep3() 方法后会将状态重置成 State.STEP_START (注意),表示本次的 layout 流程已经结束,为下次的 layout 流程做准备,所以需要将 mState.mLayoutStep 状态重置。

在这里插入图片描述

为什么要设置 mState.mLayoutStep 状态与 dispatchLayoutStep_n() 的3个方法做关联呢?

这与 onMeasure 阶段是否执行自动测量逻辑有关。由于存在下面两种执行情况,因此需要通过一个变量 (mState.mLayoutStep) 的状态流转来判断执行的逻辑。

  1. 当 onMeasure 阶段执行 Condition2 的自动测量时,会先执行 dispatchLayoutStep1()、dispatchLayoutStep2() 方法,在 onLayout 阶段会执行 dispatchLayoutStep3() 方法。
  2. 当 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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值