RecyclerView源码分析(二)--测量流程

RecyclerView是一个ViewGroup,那么也就是一个View。那么View的绘制过程是measure到layout到draw的一个顺序。然而一个ViewGroup的目的是盛放其它View的,那么最主要的还是其measure和layout过程。那么我们今天就来看看RecyclerView的measure过程。

PS:源码版本为androidx1.0.0版本,如果下面与你的源码有出入,请核实版本是否相同。

RecyclerView的Measure过程

如果你这个时候也打开了源码,你应该会发现RecyclerView的onMeasure方法很长。那么我们先将其分情况讨论。

  1. 没有LayoutManager的情况
  2. 有LayoutManager并开启了自动测量
  3. 有LayoutManager但没有开启自动测量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        // 1. 没有LayoutManager的情况
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.mAutoMeasure) {
        // 2. 有LayoutManager并开启了自动测量
        ……
    } else {
        // 3. 有LayoutManager但没有开启自动测量
        ……
    }
}

第一种情况十分简单,就是执行了defaultOnMeasure方法。里面就是计算并设置了RecyclerView的长宽值。下来我们介绍另外两种情况。

RecyclerView的自动测量过程

在RecyclerView的早期版本,当你为其设置了wrap_content值,但在其中的内容改变的时候,RecyclerView并不能改变其大小来适应内部的内容。因此后来加入了自动测量机制,来解决这个问题。而且现在我们常用的三个LayoutManager,在其构造函数中,均已经设置了开启自动测量。所以我们现在可以放心大胆的为RecyclerView设置wrap_content。

那么我们来看一下RecyclerView的自动测绘过程:

>LinearLayoutManager.java
    @Override
    public boolean isAutoMeasureEnabled() {
        return true;
    }

>RecyclerView.java

    protected void onMeasure(int widthSpec, int heightSpec) {
        //2. 有LayoutManager并开启了自动测量
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            // 第一部分:
            // 1) 首先执行LayoutManager的onMeasure方法。
            // 2) 检查如果width和height都已经是精确值,那么就不用再根据内容进行计算所
            // 需要的width和height,那么跳过之后的步骤。如果有其中任何一个值不是精确值,
            // 则进入到下面计算所需长宽的步骤。
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            // 第二部分:
            // 1) 开启布局流程计算出所有Child的边界
            // 2) 然后根据计算出的Child的边界计算出RecyclerView的所需width和height
            // 3) 检查是否需要再次测量
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }

            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

            // 布局过程结束,根据Children中的边界信息计算并设置RecyclerView长宽的测量值
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // 检查是否需要再次测量。如果RecyclerView仍然有非精确的宽和高,或者这里还有至
            // 少一个Child还有非精确的宽和高,我们就需要在此测量。
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } 
    }

自动测绘过程可以分为两部分:

第一部分:

  1. 首先执行LayoutManager的onMeasure方法。
  2. 检查如果width和height都已经是精确值,那么就不用再根据内容进行计算所需要的width和height,那么跳过之后的步骤。如果有其中任何一个值不是精确值,则进入到下面计算所需长宽的步骤。

第二部分:

  1. 开启布局流程,计算出所有Child的边界。
  2. 然后根据计算出的Child的边界计算出RecyclerView的所需width和height,并设置。
  3. 检查是否需要再次测量,如果需要则在此进行测量。

注意:
因为自动绘制过程中执行了布局流程,那么在之后布局的时候会检查是否已经进行过布局流程,如果已经进行过布局流程,则会跳过进行过的布局流程,不会造成重复操作。(布局流程有三步)

RecyclerView的非自动测绘流程

当我们使用系统提供的那三个LayoutManager的时候,默认是开启自动测绘的。除非,你在初始化LayoutManager之后,自己通过setAutoMeasureEnabled(false)方法设置成false。不然不会走到非自动测绘流程的。但是如果我们是使用的自己自定义的LayoutManager,而且我们自定义的LayoutManager又没有在初始化的时候开启自动测绘,那么默认将会是不开启自动测绘。这个时候会走到非自动测绘流程。

那么接下来看一看非自动测绘流程。


 


    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout.isAutoMeasureEnabled()) {
            .......
        } else {
            // 第一部分:如果RecyclerView已经设置了Size固定,则执行LayoutManager的onMeasure方法
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // 第二部分:
            // 1) 如果在测量的过程中有数据有更新,则先处理更新的数据
            // 2) 执行自定义测量流程,这需要自定义的LayoutManager实现onMeasure方法。
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                onEnterLayoutOrScroll();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    // consume remaining updates to provide a consistent state with the layout pass.
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
                // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
                // this means there is already an onMeasure() call performed to handle the pending
                // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
                // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
                // because getViewForPosition() will crash when LM uses a child to measure.
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }
            // 处理完新更新的数据,然后执行自定义测量操作。
            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear
        }
    }

RecyclerView的非自动化测绘流程也可以分为两部分:

第一部分:
在固定尺寸模式下,直接执行LayoutManager的onMeasure方法。

第二部分:
不在固定尺寸模式下

  1. 如果在测量的过程中有数据有更新,则先处理更新的数据
  2. 执行自定义测量流程,这需要自定义的LayoutManager实现onMeasure方法。

总结

一般情况下,进入的都是自动测量模式。

最不常见的是没有设置LayoutManager的模式。

最后需要注意的是:我们自定义LayoutManager的时候要根据自己的需求,决定是否要开启自动测量。如果需要开启,则要主动调用setAutoMeasureEnabled(true),否则默认是不开启的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值