RecyclerView源码阅读(一) onMeasure、onLayout、onDraw

本文详细分析了RecyclerView的onMeasure、onLayout和onDraw方法,探讨了其内部如何处理LayoutManager的自动测量、数据更新、布局步骤及动画执行。着重解释了LayoutManager在不同情况下的测量逻辑,包括默认情况、开启自动测量和未开启自动测量的处理,以及onLayoutChildren方法中如何填充子视图。此外,还提到了ItemDecoration的onDraw方法在绘制过程中的作用。


RecyclerView是google提出ListView的替代品。更强大的缓存支持,Grid模式和Horizantal模式展示。

1. 基本使用方式:

RecyclerView.setLayoutManager()
RecyclerView.setAdapter()
RecyclerView.setItemAnimator()
RecyclerView.addItemDecoration()

解释说明。

  • setLayoutManager:必选项,设置 RV 的布局管理器,决定 RV 的显示风格。常用的有线性布局管理器(LinearLayoutManager)、网格布局管理器(GridLayoutManager)、瀑布流布局管理器(StaggeredGridLayoutManager)。
  • setAdapter:必选项,设置 RV 的数据适配器。当数据发生改变时,以通知者的身份,通知 RV 数据改变进行列表刷新操作。
  • addItemDecoration:非必选项,设置 RV 中 Item 的装饰器,经常用来设置 Item 的分割线。
  • setItemAnimator:非必选项,设置 RV 中 Item 的动画。

RecyclerView本身也是一个自定义View,自然离不开三大流程。下面咱们分别看下它的onMeasure、onLayout、onDraw方法。RecyclerView将每一个 ItemView 显示到屏幕上,在显示和滑动过程中,通过缓存复用来提升整体性能。

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {
   
   
        ... ...
}

2. onMeasure方法

首先看下onMeasure方法

	protected void onMeasure(int widthSpec, int heightSpec) {
   
   
        if (mLayout == null) {
   
   
            // 第一种情况,当LayoutManager为空时
        }
        if (mLayout.isAutoMeasureEnabled()) {
   
   
            // 第二种情况,LayoutManager开启自动测量。
        } else {
   
   
            // 第三种情况,LayoutManager没有开启自动测量
        }
    }

方法比较长。这里我将它分为3种情况,我将简单解释这三种情况:

  1. mLayout 即 LayoutManager 的对象。我们知道,当 RecyclerView 的 LayoutManager 为空时,RecyclerView 不能显示任何的数据,在这里我们找到答案。

  2. LayoutManager 开启了自动测量时,这是一种情况。在这种情况下,有可能会测量两次。

  3. 第三种情况就是没有开启自动测量的情况,这种情况比较少,因为 RecyclerView 为了支持 warp_content 属性,系统提供的 LayoutManager 都开启自动测量的,不过还是要分析的

2.1 LayoutManager == null的情况

这种情况下比较简单,我们来看看源码:

        if (mLayout == null) {
   
   
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }

这里是调用了 defaultOnMeasure 方法,

    void defaultOnMeasure(int widthSpec, int heightSpec) {
   
   
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }

在 defaultOnMeasure 方法里面,主要是通过 LayoutManager 的 chooseSize 方法来计算宽高,最后调用 setMeasuredDimension 方法来设置宽高。下面来看下 chooseSize 的具体逻辑:


        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);
            }
        }

这里主要是根据不同的设置,来返回最终的大小。这块逻辑不是很懂的读者可以阅读前面提到的文章,里面详细解读了。但是这里有个问题需要指出来的就是没有测量子 view 的大小,这也是白屏的原因。因为 RecyclerView 的绘制其实是委托给 LayoutManager 来管理呢,LayoutManager = null 的情况下测量子 view 没有任何的意义。

2.2 LayoutManager 开启了自动测量

在分析这种情况之前,我们先对了解下mState.mLayoutStep。这个变量有几个取值情况,我们分别来看看:

取值 含义
State.STEP_START mState.mLayoutStep 的默认值,这种情况下,表示 RecyclerView 还未经历 dispatchLayoutStep1,因为 dispatchLayoutStep1 调用之后mState.mLayoutStep 会变为 State.STEP_LAYOUT。
State.STEP_LAYOUT 当 mState.mLayoutStep 为 State.STEP_LAYOUT 时,表示此时处于 layout 阶段,这个阶段会调用 dispatchLayoutStep2 方法 layout RecyclerView 的children。调用 dispatchLayoutStep2 方法之后,此时 mState.mLayoutStep 变为了 State.STEP_ANIMATIONS。
State.STEP_ANIMATIONS 当 mState.mLayoutStep为 State.STEP_ANIMATIONS 时,表示 RecyclerView 处于第三个阶段,也就是执行动画的阶段,也就是调用 dispatchLayoutStep3方法。当 dispatchLayoutStep3 方法执行完毕之后,mState.mLayoutStep 又变为了 State.STEP_START。

从上表中,我们了解到 mState.mLayoutStep 的三个状态对应着不同的 dispatchLayoutStep 方法。

        if (mLayout.isAutoMeasureEnabled()) {
   
   
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            /**
             * This specific call should be considered deprecated and replaced with
             * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
             * break existing third party code but all documentation directs developers to not
             * override {@link LayoutManager#onMeasure(int, int)} when
             * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
             */
             //1.mLayout.onMeasure方法
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
   
   
                return;
            }
       // 2.开始测量
            if (mState.mLayoutStep == State.STEP_START) {
   
   
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
       // 3.第二次
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
   
   
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        }

我们先来看下 LayoutManager#onMeasure() 方法。

        public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
                int heightSpec) {
   
   
            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        }

发现调用的 RecyclerView 的 defaultOnMeasure 方法,测量自己本身的大小。

dispatchLayoutStep1方法

咱们再接着往下走,看下dispatchLayoutStep1。这个方法和RV的动画息息相关。下面是 dispatchLayoutStep1() 方法的具体逻辑:

 /**
     * The first step of a layout where we;
     * - process adapter updates
     * - decide which animation should run
     * - save information about current views
     * - If necessary, run predictive layout and save its information
     */
    private void dispatc
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值