RecyclerView源码分析(二)测绘流程上篇

说到安卓的测绘流程,肯定会想到安卓View绘制三大流程,measure、layout、draw。通过分析View的这三大流程,就可以大概洞悉一个View是怎么从无到有的。万变不离其踪,所以分析RecycleView也按照这个思路进行。

Measure

分析Measure过程,我们直接查看OnMeasure方法。这里我们由高层到低层逐步分析每一个部分。

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.isAutoMeasureEnabled()) {
            。。。
        } else {
            。。。
        }
    }

部分细节代码被省略了,我们可以清晰的看到一个流程

  1. 类型为LayoutManager的mLayout为空的情况下
    对应我们没有设置LayoutManager,这是measure过程会直接执行defaultOnMeasure方法
    void defaultOnMeasure(int widthSpec, int heightSpec) {
        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);
    }
    
    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:
                    //对应固定宽高 和 match_parent
                    return size;
                case View.MeasureSpec.AT_MOST:
                    //对应wrap_content
                    return Math.min(size, Math.max(desired, min));
                case View.MeasureSpec.UNSPECIFIED:
                default:
                    return Math.max(desired, min);
            }
        }

这里直接根据设置的padding和最小宽高,结合布局参数的值算出默认的尺寸。如果我们没有设置LayoutManger,那么就是这样的测量逻辑。

  1. LayoutManager的isAutoMeasureEnabled判断
        /**
         * This field is only set via the deprecated {@link #setAutoMeasureEnabled(boolean)} and is
         * only accessed via {@link #isAutoMeasureEnabled()} for backwards compatability reasons.
         */
        boolean mAutoMeasure = false;

isAutoMeasureEnabled是LayoutManager的属性,表示是否开启自动测试。我们发现系统提供的LayoutManager,不管是常用的 LinearLayoutManager还是GridLayoutManager都通过冲重写isAutoMeasureEnabled方法把它设置成为true。
所以使用系统的控件,都是走的这里面的逻辑。
接下来分别看下开启与否自动测量这两种情况的代码。

isAutoMeasureEnabled为true情况
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

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

            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            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);
            }
        }
  1. 调用LayoutManager的onMeasure方法。
    内部会直接调用RecyclerView#defaultOnMeasure,这个方法上面也分析过。按照RecyclerView的设计,如果采用自动测量(mAutoMeasure为true),那么就不要继承LayoutManager的onMeasure方法。可以看出写开源控件每一个方法的调用都应该有清晰的职责和抽象。
  2. 如果是measureSpecMode是Exactly,也就是尺寸都是EXACTLY,也就是固定尺寸会match_parent,这时候recycleView的宽高已经确认了,不需要再测量了,直接return。或者没有设置Adatper,也就是没有数据,也直接return。这里我们大概猜到开启自动测量的不同之处了,自动测量会内部根据各个item的宽高算出外部RecyclerView的宽高,如果是wrap_content模式的。
  3. 接下来,因为mState.mLayoutStep 的初始状态就是State.STEP_START。所以会分别执行dispatchLayoutStep1和dispatchLayoutStep2。看方法名就知道这是这是一个连续的步骤,这个一共分三步,还有dispatchLayoutStep3。共同完成了RV的测量布局工作。下面会详细的讲解这三个步骤。经过这两个步骤,我们就已经完成了布局,因为要支持wrap_content嘛,需要提前知道各个item的宽高。调用mLayout.setMeasuredDimensionFromChildren设置RecyclerView的布局参数。
  4. 根据shouldMeasureTwice判断是否会测量两次,看下LinearManger里怎么实现的。从下面的方法可以看出,如果RV的宽高都是wrap_content并且子view至少有一个宽高设置的都不是固定尺寸。才会进行二次测量。这里对应的场景就是子view需要依赖外部的RecyclerView的宽高。比如子View的宽是match_parent,依赖了父View的宽。
    @Override
    boolean shouldMeasureTwice() {
        return getHeightMode() != View.MeasureSpec.EXACTLY
                && getWidthMode() != View.MeasureSpec.EXACTLY
                && hasFlexibleChildInBothOrientations();
    }
    
    boolean hasFlexibleChildInBothOrientations() {
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final ViewGroup.LayoutParams lp = child.getLayoutParams();
                if (lp.width < 0 && lp.height < 0) {
                    return true;
                }
            }
            return false;
        }
isAutoMeasureEnabled为false情况
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                ();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
                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
  1. 看到了常用的mHasFixedSize的使用,他表示如果RV的大小不受适配器内容的影响。那么直接调用LayoutManager的onMeasure方法测量。对应系统提供的LinearLayoutManager,就是直接调用了上面提到的defaultOnMeasure方法。
  2. 又出现了mAdapterUpdateDuringMeasure和mRunPredictiveAnimations两种情况,表示测量期间刷新了Adapter和预测动画部分。mAdapterUpdateDuringMeasure在调用局部刷新的时候会设置成true。这块和动画相关,后面会进行讲解。
  3. 看到了熟悉的mAdapter.getItemCount(),这里获取的item的数量。并直接调用了LayoutManager的onMeasure方法进行测量,可见如果不需要自动刷新,那么我们需要实现onMeasure方法,实现我们自己的测绘工作,后面我们会自己实现一个LayoutManager。
  4. startInterceptRequestLayout/stopInterceptRequestLayout和onEnterLayoutOrScroll/onExitLayoutOrScroll一对类似开关的方法。startInterceptRequestLayout会设置mInterceptRequestLayoutDepth变量,阻止RequestLayout的调用。onEnterLayoutOrScroll会通过mLayoutOrScrollCounter加一,通过isComputingLayout方法判断是否正在测量中。可以理解为一组开关。
public boolean isComputingLayout() {
   return mLayoutOrScrollCounter > 0;
}
@Override
public void requestLayout() {
    if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
        super.requestLayout();
    } else {
        mLayoutWasDefered = true;
    }
}

通过分析没有开启自动测量的代码,发现主要的区别在于是否支持了wrap_content的功能。没有开启自动测量,我们就需要在自己的LayoutManager中通过重写LayoutManager的onMeasure自己实现自己的测量逻辑。而开启了自动测量,我们就不需要,也不要重写LayoutManager的onMeasure了。

Layout

我们看下onLayout的源码

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }
    
    void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            //前 2 个步骤是在 onMeasure 中完成的,但由于大小的变化,我们似乎必须再次运行。
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

onLayout方法内部直接调用了dispatchLayout方法,重头戏在dispatchLayout方法中。

  1. 先看到了两个判断,这也验证了没有设置LayoutManger,不显示任何东西的。在onMeasure种也进行了类似的判断,不设置LayoutManager,onMeasure会直接进行defaultOnMeasure,而onLayout会直接return,不进行任何操作。
  2. 又看到了我们熟悉的dispatchLayoutStep系列方法,只是多了一个dispatchLayoutStep3(),这里主要是根据mState.mLayoutStep的状态进行操作。我看先看下这个状态的意义。
取值意义
STEP_START还未开始自动测量,是默认值,这种情况下,会执行dispatchLayoutStep1
STEP_LAYOUT执行完成dispatchLayoutStep1后,进行设置。表示已经执行完成dispatchLayoutStep1
STEP_ANIMATIONS执行完成dispatchLayoutStep2后,进行设置。表示已经执行dispatchLayoutStep2,将要执行dispatchLayoutStep3

通过对上面状态的分析,可以看到dispatchLayout中也会执行onMeasure中的dispatchLayout系列分方法,并执行单独的dispatchLayout3。

什么情况下执行onMeasure后,mState.mLayoutStep还未STEP_START呢。

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

为什么这种情况下,就不需要在onMeasure中提前进行dispatchLayoutStep呢。分析下这是什么状态,在宽高方向有任何一个是wrap_content时,都需要提前进行dispatchLayoutStep。
RecycleView怎么支持wrap_content呢,上面也提到过。当然需要知道里面显示的item的显示细节,才能设置自己的自适应。所以这里可以看出,如果RV需要支持wrap_content,需要在onMeasure中提前进行layout,如果不需要也就是宽高都是确定的,那么就在onLayout在进行dispatchLayoutStep即可。

布局的重头戏在dispatchLayoutStep系列方法里。这个系列有3个方法,我们现大致了解下这几个方法。

方法大致用途
dispatchLayoutStep1预布局阶段,也保存一些当先item view的一些状态
dispatchLayoutStep2进行真正的测量和布局
dispatchLayoutStep3布局完成后,根据dispatchLayoutStep1中保存的状态,进行动画

可以看出1、3方法,主要进行了提前的状态保存和使用,发生在2操作的前后。2进行真正的测量后,3根据1的存储的信息,进行动画和清理工作。这就是大致的整体工作流程。1和3的内部实现主要和动画有关。以后介绍动画的时候,测量和布局的核心逻辑都在dispatchLayoutStep系列方法中,这部分由于篇幅原因,放到下一章分析。

Draw

    @Override
    public void draw(Canvas c) {
        super.draw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
    }

先看RecycleView的draw,首先调用了super.draw©,这里执行父级绘制默认的绘制流程。先绘制背景,再执行onDraw,再执行dispatchDraw。
因为在dispatchLayoutStep2中已经通过attachViewToParent加入了父类viewGroup的children里面了,所以draw方法内也会直接通过dispatchDraw绘制children内部每个item。
这里我们看下onDraw方法。这里主要绘制RecycleView本身。

    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

整体逻辑比较简单。draw和onDraw中都看到了mItemDecorations的使用。
先调用了onDraw内部的mItemDecorations的onDraw方法,这时候还没绘制child,所以是在child的下层绘制的。
再执行draw内部的onDrawOver方法。这时执行完super.draw()已经绘制完child了,所以会绘制在child的上层。

总结

  1. RecyclerView的measure分两种情况,LayoutManager开没开启autoMeasure模式,系统自带的LayoutManager都是开启了这个模式的。如果开启了autoMeasure模式,长宽有一个不是EXACTLY模式的情况下。在measure过程会提前进行dispatchLayoutStep的前两部计算,以支持wrap_content,也就是需要提前计算出子View的高度,以判断自己需要自适应的尺寸。如果没有开启这个模式,会根据LayoutManager的onMeasure方法进行测量。这就要看我们自己的实现了。需要自己实现wrap_content的效果。
  2. RecyclerView的layout也是执行了dispatchLayoutStep系列方法,他会确保执行1-3各个步骤的方法,通过当前的状态判断执行到哪儿个状态了。
  3. RecyclerView的draw通过父类viewgroup的dispatchDraw完成子类的绘制,并且实现了自己的ItemDecorations绘制功能。
  4. LayoutManager的onMeasure()和isAutoMeasureEnabled()方法,不要同时重写。在和isAutoMeasureEnabled()返回true时,会走自动测绘逻辑。这时执行的onMeasure时使用默认的实现,如果这时候也自己实现了onMeasure,是不符合系统的设计的。我们不重写isAutoMeasureEnabled(),返回false,那么测量的工作就交给我们自己实现的onMeasure方法了。
    通过这篇文章,我们知道了RV时怎么绘制布局绘画各个item的。这时RV重要的基础,也是我们接下里分析各个功能控件的基石。

下一篇会介绍dispatchLayoutStep系列方法,介绍最核心的绘制流程。本篇相当于一个壳,下一篇讲解内部的核。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值