RecyclerView源码学习笔记(三)RecycleView的绘制过程onMeasure,onLayout,onDraw

前言

前两篇讲了RecyclerView的构造函数和setLayoutManager以及setAdapter方法,这篇就开始学习RecyclerView的真正显示过程

  • RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法
  • RecyclerView源码学习笔记(二)setAdapter

内容

RecyclerView继承自ViewGroup,所以也遵守一般view的绘制过程,首先会调用onMeasure方法,我们就先看onMeasure方法

onMeasure

onMeasure的作用是决定自身和子view的尺寸,这点要事先清楚。

好,直接上源码,代码不短也不长,直接全部贴上了吧

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        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.
             */
            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();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            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);
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom 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
        }
    }

老规矩,一行一行来看。

最开始会判断mLayout是不是null,也就是有没有设置LayoutManager,如果没有就调用defaultOnMeasure(widthSpec, heightSpec),就是只对RecyclerView进行measure,不处理子view,然后就返回了。当然我们这里有设置LayoutManager(此文章系列都是用LinearLayoutManager),所以接下来要判断mLayout.isAutoMeasureEnabled()返回值,这个isAutoMeasureEnabled方法有必要讲一下,因为注释有一大堆,注释那么多,肯定很重要,这里就翻译一下注释,也算是知道了这个方法的作用。

翻译如下:

首先这个方法的返回值指定了RecyclerView在measure的过程中是使用AutoMeasure机制还是让LayoutManager的onMeasure去完成measure工作,返回true表示使用AutoMeasure机制。默认情况下,LayoutManager会返回false,但是,它的子类都是返回true,包括LinearLayoutManager。当你确定要返回false的时候,LayoutManager的子类需要重写onMeasure方法,因为RecyclerView的measure工作会全权交个LayoutManager的onMeasure方法去做。如果返回true,则你实现的LayoutManager的子类不需要实现onMeasure方法。

所以这个AutoMeasure机制又是个啥东西呢?文档是这样介绍的:

AutoMeasure使用一种简单且令人满意的方法去处理RecyclerView的layout过程,包括被LayoutManager封装的子view的布局过程,也就是通过LayoutManager的onLayoutChildren方法来实现子view的布局,然后通过子view的尺寸和位置来确定RecyclerView的尺寸。更详细的定义如下:

  1. 在RecyclerView的onMeasure方法中,如果提供的长宽的mode都是EXACTLY,RecyclerView就直接设置指定的值,然后返回。
    1. 如果长和宽两者中有任何一个不是EXACTLY,RecyclerView就开始layout过程(WTF!在onMeasure方法中就就进行layout?事实证明确实是这样搞的。。。)。首先会处理那些还没有被处理的Adapter操作,比如add,remove,然后决定是否要进行预布局(这个预布局是什么呢?就是为预动画(predictive animator)服务的,那这个预动画又是什么呢?就是让你看到item移除,添加,和改变的整个过程,详细可以参考google开发者大会视频,链接在这里RecyclerView Animations and Behind the Scenes (Android Dev Summit 2015))。如果决定要预布局,则首先会调用LayoutManager的onLayoutChildren方法,并将State类的isPreLayout方法设置为true,在这个阶段LayoutManager的getHeightgetWidth方法返回的仍然是上次layout时候RecyclerView的width和height。在处理完预操作之后,RecyclerView会调用onLayoutChildren方法,并将State的isMeasuring设置为true,将isPreLayout设置为false。这时候,LayoutManager可以通过调用getHeightgetHeightModegetWidthgetWidthMode来获取测量结果。
    2. 在layout计算之后,RecyclerView通过计算child view的边界(加上了RecyclerView的padding)来设定自身的measured width和height。LayoutManager可以重写setMeasuredDimension(Rect,int,int)方法,来选择不同的值,举个例子,GridLayoutManager重写了这个方法,来处理如下的情况,用户指定了grid有3列,但是实际上却只有两个数据条目,在这种情况下,还是应该将宽度设置为3列而不是2列。
    3. 接下来,任何对RecyclerView的onMeasure的调用都会运行onLayoutChildren方法,并伴随着State的isMeasuring()返回trueisPreLayout()返回false。RecyclerView会为了动画而去管理好那些真正被增加,删除,移动,改变的view,这样LayoutManager就不需要担心他们,并且在处理每一个onLayoutChildren的时候好像是在处理最后一个一样(一脸闷逼啊)
    4. 当measure完成,且RecyclerView的onLayout方法被调用,RecyclerView会检查是否已经在measure过程中做了layout计算,如果做了,它会重用那些信息,如果发现上次的测量结果和最终的值有不一样,或者adapter数据在measure和onlayout过程中发生改变,它可能仍然会决定再跑一次onLayoutChildren。最后就是跑动画。

好了,讲完了,如果有认真看的同学,肯定会一脸闷逼,暂时不管他,先看下去。

回到onMeasure()方法,在这里mLayout.isAutoMeasureEnabled()是返回true,我们就看true下面是怎么走。其实下面的代码都是按照上面的机制走的,有兴趣的同学可以自己一一对照。

if (measureSpecModeIsExactly || mAdapter == null) {
   return;
}

如果长和宽都是EXACTLY,就直接返回,当然我们这里不是,所以继续往下走。

if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
}

我们这里假设是第一次运行,mState.mLayoutStep的值就是初始值State.STEP_START,所以接下来要走dispatchLayoutStep1(),此君代码有点多,我就一部分一部分贴吧。。

mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
....

做了以下事情

  1. 判断当前的layoutStep是不是State.STEP_START,当然是。
  2. fillRemainingScrollValues(mState)就是如果当前正在scroll,则把没有滑完的距离记录到mState
  3. mState.mIsMeasuring设置为false,表示当前还没开始measure
  4. eatRequestLayout(),这个方法会给mEatRequestLayout值加一,然后再设置mLayoutRequestEaten的值,目前对这两个值的作用还不是很清楚,应该是会丢弃requestLayout的请求,然后再某个时间再进行layout。
  5. 清空mViewInfoStore,这个变量是用来保存view动画有关的数据,尼玛名字能不能取的好一点
  6. onEnterLayoutOrScroll(),会对变量mLayoutOrScrollCounter加一,表示进行布局和scroll的次数,具体的作用还是要看下去才知道。
  7. processAdapterUpdatesAndSetAnimationFlags(),这个方法中会消耗掉还未被处理的Adapter操作,然后决定跑什么类型的动画,贴一下代码
     private void processAdapterUpdatesAndSetAnimationFlags() {
        if (mDataSetHasChangedAfterLayout) {
            // Processing these items have no value since data set changed unexpectedly.
            // Instead, we just reset it.
            mAdapterHelper.reset();
            mLayout.onItemsChanged(this);
        }
        // simple animations are a subset of advanced animations (which will cause a
        // pre-layout step)
        // If layout supports predictive animations, pre-process to decide if we want to run them
        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
        mState.mRunSimpleAnimations = mFirstLayoutComplete
                && mItemAnimator != null
                && (mDataSetHasChangedAfterLayout
                || animationTypeSupported
                || mLayout.mRequestedSimpleAnimations)
                && (!mDataSetHasChangedAfterLayout
                || mAdapter.hasStableIds());
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                && animationTypeSupported
                && !mDataSetHasChangedAfterLayout
                && predictiveItemAnimationsEnabled();
    }

做了如下事情:

  1. 首先会判断mDataSetHasChangedAfterLayout是否为true,这个变量的意思是Adapter的数据有没有被整个更新过,这个值只有在setAdapterswapAdapter,和notifyDataSetChanged方法中会被设置为true。回到代码,如果判断到mDataSetHasChangedAfterLayout,RecyclerView就会清空未完成的Adapter操作,因为数据都被整个换过了,再做这些操作是没有意义的,并调用mLayout.onItemsChanged(this),所以如果想要在数据被整个换过的时候做一些动作,可以实现LayoutManager的onItemsChanged方法。
  2. 调用predictiveItemAnimationsEnabled(),判断是否支持predictive item动画,通过阅读代码,LayoutManager本身是不支持的,但是它的子类LinearManager在大多数情况下是支持的,所以我们这里就把它的结果看做是true,下面就要走mAdapterHelper.preProcess(),这个方法会内部会先把没有处理过的item操作重新排序,主要是把move操作排到最后,因为move如果放在中间的话,操作起来比较麻烦。然后再逐个处理里面的操作,操作是以UpdateOp来封装的,操作种类有ADD,REMOVE,MOVE,UPDATEpreProcess()方法对操作处理,会先把操作放到一个mPostponedList中,然后调用Callback的实现者去真正完成这些操作,而这个实现者就是RecyclerView,但是RecyclerView在处理这些操作的时候,并不会马上去更新ui,而是去更新viewhoder里面的position信息。
  3. 给animationTypeSupported赋值,因为我们这里是第一次运行,所以都是false,后面的mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations也是false。

好了dispatchLayoutStep1()方法的第一部分已经讲完了。下面贴第二部分

        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

        if (mState.mRunSimpleAnimations) {
            // Step 0: Find out where all non-removed items are, pre-layout
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                    continue;
                }
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                        && !holder.shouldIgnore() && !holder.isInvalid()) {
                    long key = getChangedHolderKey(holder);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }
        if (mState.mRunPredictiveAnimations) {
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // temporarily disable flag because we are asking for previous layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = didStructureChange;

            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (viewHolder.shouldIgnore()) {
                    continue;
                }
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
  • saveFocusInfo()保存当前获取焦点的view的信息。
  • 给一些变量赋值,这里我们由于是第一次跑,也没有对item的操作行为,所以 mState.mTrackOldChangeHoldersmState.mRunSimpleAnimations,mItemsAddedOrRemoved,mItemsChanged, mState.mInPreLayout都是false。下面的if(mState.mRunSimpleAnimations)if(mState.mRunPredictiveAnimations)也都跑不进去。这两个if我会在后面讲LinearLayoutManager的时候再研究
  • clearOldPositions,将所有viewholdermOldPositionmPreLayoutPosition复位为NO_POSITION
  • 调用resumeRequestLayout方法,本来在这个方法里面是把之前吃掉的layout请求吐出来,也就是调用dispatchLayout()进行真正的layout,但是由于传进去的参数是false,也就是不进行layout,所以这里只是对mEatRequestLayoutmLayoutRequestEaten这两个变量进行操作。
  • mState.mLayoutStep 设置为 State.STEP_LAYOUT,表示接下来要进行真正的layout

到这里dispatchLayoutStep1()方法就结束了。

回到onMeasure()方法

接下来会调用mLayout.setMeasureSpecs(widthSpec,heightSpec)里面做的事情是分别判断width和height的mode是不是UNSPECIFIED,如果是,就再看ALLOW_SIZE_IN_UNSPECIFIED_SPEC是不是false,如果是false就将height和width设置为0,这个ALLOW_SIZE_IN_UNSPECIFIED_SPEC只有在大于等于M的机器上才是true
然后将mState.mIsMeasuring设施为true,接着调用dispatchLayoutStep2()方法,我们这里看下这个源码

    /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
        eatRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
    }

从注释可以知道,这里是真正进行layout的地方,然后还可能跑多次。
我们从第4行开始,这里调用了mAdapterHelper.consumeUpdatesInOnePass(),就是将mPendingUpdates里面的操作消耗掉,这个方法最后会调用到LayoutManager的onItemsAdded,onItemsRemoved,onItemsUpdated,onItemsMoved,但是LayoutManager和LinearLayoutManager都没有在这些方法里做什么事情,猜想应该这些方法跟真正的布局child没有关系。
接下里看这部分

  // Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
  1. 先把mState.mInPreLayout设为false,然后调用mLayout.onLayoutChildren()方法,这个方法是真正对child进行layout的地方,不过也不是只要跑这个方法就会真正对child进行布局,比如要进行predictive animator。关于onLayoutChildren的源码这里也先不讲了,后面专门讲LinearLayoutManager的时候再讲。
  2. 下面再回到onMeasure方法,在调用完dispatchLayoutStep2()之后,会调用LayoutManager的setMeasuredDimensionFromChildre()方法,这个方法会根据child的布局算出RecyclerView的边界,不过LayoutManager可以根据自己的需求做特殊处理,如前面提到的GridLayoutManager。
  3. 接下来判断要不要再进行一次measure,通过LayoutManager的shouldMeasureTwice()方法来判断,默认是false,而sdk中也只有LinearLayoutManager才有重写这个方法,返回true的条件是RecyclerView的height和width没有一个mode是Exact,且至少有一个孩子的height和width的mode也没有一个是Exact。二次measure做的事情和前面的一样,就不再赘述。

我这里只讲mLayout.mAutoMeasure等于true的情况,因为目前SDK自带的LayoutManager子类都是返回true,也就是说支持autoMeasure模式。RecyclerView的onMeasure方法就讲完了,按照view的渲染顺序,接下来调用onLayout方法。

onLayout

onlayout方法的作用是对childview进行布局,和onMeasure是差别的,因为onMeasure还会对自身进行测量,而onLayout只对child进行布局。

废话不多讲,直接上代码

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

看了代码,我只想说一句,so easy,妈妈再也不用担心我的学习。。。,想不到竟然只有四行代码,而且其中2行还是跟debug有关,真是太幸福了。好了直接看dispatchLayout()方法,先看注释,经过翻译,意思如下

封装了 layoutChildren()对layout过程中引起的动画变化的处理,动画的工作基于如下假定:显示的item有五种类别,

  • PERSISTENT: 在layout之前还是之后,那些依然可见的item
  • REMOVED: layout之前可见,然后被app移除的items
  • ADDED: layout之前不存在,后来被app添加的items
  • DISAPPEARING: 在layout之前之后一直存在,但是在layout之后被移出屏幕的item,比如当前屏幕上有10个item,然后你在第五个位置添加了一个item,这样第10个item就会被挤到屏幕外,这样第10个item就是DISAPPEARING item。
  • APPEARING: 在layout之前不可见,但在layout之后移到屏幕内的item

所有的操作都是为了确定在layout前后哪些item是可见的,哪些是不可见的,然后分别给每个item指定以上五种种类中的一种, 然后根据不同的种类,使用不同的方法来构建动画,

PERSISTENT通过RecyclerView.ItemAnimator.animatePersistence(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
DISAPPEARING通过RecyclerView.ItemAnimator.animateDisappearance(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
APPEARING通过RecyclerView.ItemAnimator.animateAppearance(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
被改变的item通过RecyclerView.ItemAnimator.animateChange(RecyclerView.ViewHolder,RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo)来构建

好了注释讲完了,应该可以知道一点信息了。接下来看源码

    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()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

操作如下:

  1. 判断adapter和layoutManager是不是null,是null就返回
  2. mState.mIsMeasuring设置为false
  3. 判断当前mState.mLayoutStep是不是等于State.STEP_START,由于之前在onMeasure中已经设置为State.STEP_ANIMATIONS,所以这里进不去,从我实际debug来看,最后会跑进else if这个代码块,也就是LayoutManager中的尺寸和RecyclerView的尺寸不一样,需要重新测量,就是调用mLayout.setExactMeasureSpecsFrom(this)方法,将RecyclerView的尺寸传给LayoutManager,然后再跑一边dispatchLayoutStep2()方法。
  4. 调用dispatchLayoutStep3(),dispatchLayoutStep3的注释是说该方法是layout的最后一步,主要是保存view的动画信息,启动动画,做一些必要的清理工作。好了还是贴源码,比较多,他妈也不算多,跟framework一个方法动不动就几百行来说真是小意思。
     /**
     * The final step of the layout where we save the information about views for animations,
     * trigger animations and do any necessary cleanup.
     */
    private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        eatRequestLayout();
        onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
        ...
        }

        mLayout.removeAndRecycleScrapInt(mRecycler);
        mState.mPreviousLayoutItemCount = mState.mItemCount;
        mDataSetHasChangedAfterLayout = false;
        mState.mRunSimpleAnimations = false;

        mState.mRunPredictiveAnimations = false;
        mLayout.mRequestedSimpleAnimations = false;
        if (mRecycler.mChangedScrap != null) {
            mRecycler.mChangedScrap.clear();
        }
        if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
            // Initial prefetch has expanded cache, so reset until next prefetch.
            // This prevents initial prefetches from expanding the cache permanently.
            mLayout.mPrefetchMaxCountObserved = 0;
            mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
            mRecycler.updateViewCacheSize();
        }

        mLayout.onLayoutCompleted(mState);
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        mViewInfoStore.clear();
        if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
            dispatchOnScrolled(0, 0);
        }
        recoverFocusFromState();
        resetFocusInfo();
    }

我们直接跳到if(mState.mRunSimpleAnimations)因为这是第一次运行,所以不会跑动画,这个if进不去,我就把代码去掉了,在后面的文章中再仔细研究。接下来直接跳到mLayout.removeAndRecycleScrapInt(mRecycler)此方法将scrap的view从RecyclerView移除,并回收到Recycler中。接下来跳到mLayout.onLayoutCompleted(mState)此方法说明到此RecyclerView的layout工作已经完成了,LayoutManager如果需要做一些事情,可以重写这个方法,此方法只有在RecyclerView的onLayout方法末尾才会被调用。再跳到

if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
  dispatchOnScrolled(0, 0);
}

上述代码其实是判断RecyclerView有没有被滑动过,如果有,则调用那些监听scroll的Listener
最后恢复焦点,重置mState中关于焦点信息的对象。
好了,dispatchLayoutStep3()方法也说完了,因为我们跳过了动画的代码,所以比较简单,先留着吧,后面会再来讲,dispatchLayoutStep3()跑完,onLayout方法也跑完了,接下来就是onDraw

onDraw
此方法专门用于绘制RecyclerView本身的内容,不包括item,因为item会调用自身的onDraw方法。
直接贴代码吧

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

呵呵简单的不可思议,竟然只画了ItemDecoration的内容,而且只是调用了ItemDecoration的onDraw方法,那么问题来了,据我所知,ItemDecoration还有一个onDrawOver方法,那这个方法是什么时候掉的呢?当然是搜一下就好了,原来RecyclerView还实现了draw方法,而这个onDrawOver方法就是在这个方法里被调用的,代码如下

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

其实按照一般自定义view的方法,我们是不需要实现这个draw方法的,只需要实现onDraw方法,那么为什么这里要实现draw方法呢?那就看下这里draw方法做了什么事情,首先我们要清楚,在draw方法的最开始调用了super.draw也就是View的draw方法,而view的draw方法会调用onDraw方法,所以在这里整个方法的调用顺序是这样的:View.draw->RecycleView.onDraw->RecycleView.draw。好下来看一下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);
        }

首先调用super.draw,然后调用所有ItemDecoration的onDrawOver方法,所以从这里我们就可以知道,ItemDecoration的onDraw方法是画在item之前,而onDrawOver是画在item之后。
接下来

        boolean needsInvalidate = false;
        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
            final int restore = c.save();
            final int padding = mClipToPadding ? getPaddingBottom() : 0;
            c.rotate(270);
            c.translate(-getHeight() + padding, 0);
            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mTopGlow != null && !mTopGlow.isFinished()) {
            final int restore = c.save();
            if (mClipToPadding) {
                c.translate(getPaddingLeft(), getPaddingTop());
            }
            needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mRightGlow != null && !mRightGlow.isFinished()) {
            final int restore = c.save();
            final int width = getWidth();
            final int padding = mClipToPadding ? getPaddingTop() : 0;
            c.rotate(90);
            c.translate(-padding, -width);
            needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
            final int restore = c.save();
            c.rotate(180);
            if (mClipToPadding) {
                c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
            } else {
                c.translate(-getWidth(), -getHeight());
            }
            needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
            c.restoreToCount(restore);
        }

        // If some views are animating, ItemDecorators are likely to move/change with them.
        // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
        // display lists are not invalidated.
        if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
                && mItemAnimator.isRunning()) {
            needsInvalidate = true;
        }

        if (needsInvalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }

其实就是画了RecyclerView四个边的边界效果,而且如果边界动画还没结束就要继续调用postInvalidateOnAnimation方法,来进行重绘,从而实现动画效果。

好啦,RecycleView的绘制过程就到这里啦,由于我也是在学习源码阶段,有些地方不是很清楚,只能先留着等全部看明白后再来补充

你可能感兴趣的:(android源码研究)