彻底搞懂RecyclerView,告别职业焦虑emo(上篇)

911 阅读12分钟

持续学习,告别职业焦虑~

每次一个告别emo小技巧,今天带来RecyclerView#LayoutManager。😏~其实是间接的进入布局三件套主题。

相信大家对于LayoutManager的使用肯定都非常熟悉了,如果还不熟悉或者不知道这个是干啥的,本篇文章可能也就不适合你了。

由于RecyclerView内部包含内容确实比较多,鉴于本人可能理解方面会存在一些偏差,欢迎同样学习RecyclerView源码的同学指正交流。手动狗头~

接下来就直接从源代码实现看一下LayoutManager能带给我们什么惊喜吧~

RecycerView#setLayoutManager(@Nullable LayoutManager layout)

这个方法本身是平常使用LayoutManager的一个入口。

public void setLayoutManager(@Nullable LayoutManager layout) {
    if (layout == mLayout) {
        return;
    }
    stopScroll();
    if (mLayout != null) {
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
        }
        mLayout.removeAndRecycleAllViews(mRecycler);
        mLayout.removeAndRecycleScrapInt(mRecycler);
        mRecycler.clear();

        if (mIsAttached) {
            mLayout.dispatchDetachedFromWindow(this, mRecycler);
        }
        mLayout.setRecyclerView(null);
        mLayout = null;
    } else {
        mRecycler.clear();
    }
    mChildHelper.removeAllViewsUnfiltered();
    mLayout = layout;
    if (layout != null) {
        if (layout.mRecyclerView != null) {
            throw new IllegalArgumentException("LayoutManager " + layout
                    + " is already attached to a RecyclerView:"
                    + layout.mRecyclerView.exceptionLabel());
        }
        mLayout.setRecyclerView(this);
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
}

上面的代码逻辑上主要处理了两件下:

  • 1、如果LayoutManager重新设置新的,则将之前的进行解绑操作,并将新的进行重新绑定。
  • 2、使用requestLayout通知布局重新执行onMeasureonLayout

如果不清楚为什么执行requestLayout会导致父容器调用执行onMeasureonLayout的同学,可以看下这篇文章,Android View 深度分析requestLayout、invalidate与postInvalidate

顺着这个思路接下来就看一下onMeasureonLayout方法。

RecyclerView#onMeasure

这个方法牵扯到很多很有用的东西,虽然牵扯方法很多,也很长,但是还是建议整体看完。

里面可能涉及到一些比较核心的方法,为了整体理解流程不中断,会在下面单独列出来代码说明。

 protected void onMeasure(int widthSpec, int heightSpec) {
     /**
 	 * 如果没有设置layout, 则使用defaultOnMeasure进行测量defaultOnMeasure方法则是根据
 	 * widthSpec和heightSpac计算出来测试模式和测试大小,在集合padding值进行一个测量。
 	 * 具体规则是:如果是EXACTLY,则直接使用测量大小,AT_MOST的话,则在	
 	 * ((paddingLeft+paddingRight)或者(paddingTop + paddingBottom)),与
 	 * (ViewCompat.getMinimumWidth(this)或者ViewCompat.getMinimumHeight(this))最大值,
 	 * 再与计算值之间取最小值。如果是UNSPECIFIED的话,则是使用((paddingLeft+paddingRight)
 	 * 或者(paddingTop + paddingBottom)),与(ViewCompat.getMinimumWidth(this)或者
 	 * ViewCompat.getMinimumHeight(this))最大值
 	 */
     if (mLayout == null) {
         defaultOnMeasure(widthSpec, heightSpec);
         return;
     }
     /**
      * RecyclerView#isAutoMeasureEnabled,这个方法决定了onMeasure计算是由LayoutManager负责,
      * 还是由RecyclerView自己负责。isAutoMeasureEnabled的默认值为false。
      * 而LayoutManager#isAutoMeasureEnabled作用是相似的,
      * 系统为我们提供的LinearLayoutManager就是直接返回true,由Layout自身负责onMeasure测量的。
      */
     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.
          */
         //=====================这是个分割线======================
         /**
          * 从LayoutManabger#onMeasure代码发现实现其实就是上面的
          * recyclerView#defaultOnMeasure方法,
          * 虽然理论上是可以使用recyclerView#defaultOnMeasure直接替代
          * LayoutManager#onMeasure,但是由于一些老旧代码的实现、引用限制,
          * 目前还不能完全被替代。当然如果是自己实现LayoutManager的话,
          * 官方并不建议复写LayoutManager#onMeasure方法。
          */	
         mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

         /**
          * 如果当前测量模式是exactly,并且没有设置adapter,则跳过下面的测量流程
          * 上面代码只测量了recyclerView的大小,并没有测量内部组件的大小
          */
         final boolean measureSpecModeIsExactly =
                 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
         if (measureSpecModeIsExactly || mAdapter == null) {
             return;
         }

         //mLayoutStep初始状态就是STEP_START
         if (mState.mLayoutStep == State.STEP_START) {
             /**
              * 首次布局,主要涉及以下操作:
              * 1、执行adapter的更新
              * 2、决定哪一个动画执行
              * 3、保存当前view的信息
              * 4、如果需要,执预布局操作,并保存相关信息
              * 
              * ===============分割线========================
              * 这里面进行了一些初始化操作,会做判断,是否处理动画执行准备工作
              */
             dispatchLayoutStep1();
         }
         // set dimensions in 2nd step. 
         // Pre-layout should happen with old dimensions for consistency
         //将之前计算的容器的高度、宽度和测量模式设置给LayoutManager
         mLayout.setMeasureSpecs(widthSpec, heightSpec);
         mState.mIsMeasuring = true;
         /**
          * 这个方法可能在被多次调用,该方法用来对子itemview进行测量和排列
          * 这里测量布局核心使用到mLayout.onLayoutChildren(mRecycler, mState);
          * 该方法需要LayoutManager自行实现。
          * 
          * ===================分割线=========================
          * 万变不离其宗,涉及到计算排列,肯定离不开view.measure和view.layout方法。
          * 跟踪下来发现主要涉及到方法:
          * measureChildWithMargins(view, 0, 0)--负责测量 和 
          * layoutDecoratedWithMargins(view, left, top, right, bottom) -负责排列
          */
         dispatchLayoutStep2();

         // now we can get the width and height from the children.
         //这里可以理解成根据计算出来的子布局的大小,重新决定出来recyclerview实际的大小
         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.
         /**
          * 判断是否需要计算两次,默认false,实际根据LayoutManager的实现类具体处理,
          * 在LinearLayoutManager中,发现判断是经过上面的处理之后,只有width和height中,
          * 还有一个值的测量模式不是Exactly(也就不是确定值),则需要进行下面的二次测量
          */
         if (mLayout.shouldMeasureTwice()) {
             //常规操作了,首先强制使用Exactly模式,将已经测量的值,进行设置到MeasureSpec中
             mLayout.setMeasureSpecs(
                     MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
             mState.mIsMeasuring = true;
             //执行上面的itemView测量排列,上面有说过
             dispatchLayoutStep2();
             // now we can get the width and height from the children.
             //通过计算出来的子布局大小,重新确定recyclerView实际大小
             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
     }
 }

dispatchLayoutStep1dispatchLayoutStep2以及dispatchLayoutStep3

在上面代码recyclerView#onMeasure中有这么一段代码,而且如果你再去阅读dispatchLayout方法的话,也会发现类似的操作。只是在dispatchLayout中多了一个dispatchLayoutStep3方法。

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

在上面的onMeasure中,我们解释了说dispatchLayoutStep1当中处理了一些状态和信息初始化,以及普通加载动画和预加载视图动画的准备工作。而dispatchLayoutStep2中处理了真实的子view的测量和排列工作。下面将具体实现详细说明下。

在进入正题之前,这里还有两个比较重要的类,需要说明下:

  • RecyclerView#State 这个类包含了当前recyclerView的一些很有用的信息,比如目标滚动位置或者获取焦点的view数据,通常情况下,RecyclerView组件需要在彼此之间传递信息,为了在组件之间提供定义良好的数据总线,RecyclerView将传递相同的状态对象到组件的回调,这些组件可以使用它来交换数据。 STEP_START(初始化状态), STEP_LAYOUT(执行完dispatchLayoutStep1之后,改变为该状态), STEP_ANIMATIONS(执行完dispatchLayoutStep2之后,改变为该状态)。
  • ViewInfoStore 这个类是用来在执行动画的时候追踪view视图的一个辅助类,说白了就是协助view视图动态的。 在ViewInfoStore中,会在不同状态对view数据和animationInfo进行分别保存。在dispatchLayoutStep1阶段,使用mViewInfoStore.addToPreLayout(holder, animationInfo);进行保存,可以发现这里保存的是preLayout。而在dispatchLayoutStep3中使用mViewInfoStore.addToPostLayout(holder, animationInfo);进行保存。并且所有的操作完成之后,会使用过mViewInfoStore.process(mViewInfoProcessCallback);进行执行动画。
dispatchLayoutStep1
private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    fillRemainingScrollValues(mState);
    mState.mIsMeasuring = false;
    startInterceptRequestLayout();
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    processAdapterUpdatesAndSetAnimationFlags();
    saveFocusInfo();
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    mState.mItemCount = mAdapter.getItemCount();
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
    if (mState.mRunSimpleAnimations) {
        /**
         * 找出所有没有删除的item,预加载item
         * int getChildCount() {
         *  return mCallback.getChildCount() - mHiddenViews.size();
    	 * }
         */
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
        	//通过childView找到Viewholder
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                continue;
            }
            /**
             * recordPreLayoutInformation方法在layout之前,由recyclerView调用。
             * Item animator在view可能执行rebound, moved or removed之前保存view的信息。
             * ItemHolderInfo 信息将在layout布局完成之后回传给animate方法。
             */
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            //addToPreLayout 方法用来追踪paylayout的item的信息                
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                long key = getChangedHolderKey(holder);
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {
        saveOldPositions();
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        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);
                }
            }
        }
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}

上面的代码,除了一些状态初始化和对状态、数据保存之外,很大代码篇幅是在两个if判断之中。if (mState.mRunSimpleAnimations) {...}if (mState.mRunPredictiveAnimations) {...}。但是通过断点和阅读代码发现,其实onMeasure的时候这两个状态都是false,所以并不会进入该代码块执行。

dispatchLayoutStep1方法在dispatchLayout方法中也会继续调用执行。对于mRunSimpleAnimationsmRunPredictiveAnimations的状态改变,其实也是在dispatchLayoutStep1中执行状态判断之前的processAdapterUpdatesAndSetAnimationFlags方法中执行的。

private void processAdapterUpdatesAndSetAnimationFlags() {
  	...
    boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
    mState.mRunSimpleAnimations = mFirstLayoutComplete
            && mItemAnimator != null
            && (mDataSetHasChangedAfterLayout
            || animationTypeSupported
            || mLayout.mRequestedSimpleAnimations)
            && (!mDataSetHasChangedAfterLayout
            || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
            && animationTypeSupported
            && !mDataSetHasChangedAfterLayout
            && predictiveItemAnimationsEnabled();
}

mItemAnimator 默认是使用的SimpleItemAnimator类,mFirstLayoutComplete设置true是在onLayoutdispatchLayout执行之后设置的。因此大胆猜测,dispatchLayoutStep1中对于mRunSimpleAnimations mRunPredictiveAnimations 只在Item执行删除、新增、内容改变的时候才会执行if里面的操作。

简单总结下dispatchLayout1方法,可以理解这个方法主要是初始和保存一些状态和item信息,方便如果预加载布局在item删除、新增、变化的时候执行动画。

dispatchLayoutStep2
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    ...
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
    //可以看到是设置mState的动态执行状态
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    //设置layoutStep状态
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    ...
    stopInterceptRequestLayout(false);
}

上面省略了其他代码,我们只关心核心代码实现。

public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}

咦~,有没有点惊喜,这个方法没有进行默认实现,需要LayoutManager的具体实现做override。那么就从LinearLayoutManager入手看看吧。

LinearLayoutManager#onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ...
    //执行清理工作
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd) {
        ...
        fill(recycler, mLayoutState, state, false);
        ...
    } else {
        ...
        fill(recycler, mLayoutState, state, false);
        ...
    }
    ...
    //为预加载动画重新设置布局-内部还是执行fill
    layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    ...
}

发现核心其实就是fill方法,继续跟进

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    ...
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
       ...
       layoutChunk(recycler, state, layoutState, layoutChunkResult);
       ...
    }
    ...
    return start - layoutState.mAvailable;
}

继续...layoutChunk

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
	...
	/**
	 * 这里引一个题外话,如果自定义LayoutManager的话,
	 * 必须要实现LayoutManager的generateDefaultLayoutParams方法。
	 */
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            //1️⃣
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            //2️⃣
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    //3️⃣
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (mOrientation == VERTICAL) {
        //略掉部分代码...主要用来做rtl、mLayoutDirection 等处理。
    } else {
        //略掉部分代码...主要用来做rtl、mLayoutDirection 等处理。
    }
    //4️⃣
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

上面代码中,我在四个比较重要的方法上面标记了数字,下面分别说一下。

  • 1️⃣ addView(view); 最终其实执行的是private void addViewInt(View child, int index, boolean disappearing),只是这里第三个参数disappearing为false。这个参数确定是否在onLayoutChilden调用的时候执行动画。只在之前是被gone掉或者remove掉的时候才有效果。最终动画处理类其实还是ItemAnimator
  • 2️⃣ addDisappearingView(view); 和上面addView执行类似,只是这里的disappearing参数是true,即会执行动画。
  • 3️⃣ measureChildWithMargins(view, 0, 0); 这个方法执行的是一个标准的view测量流程,与传统流程相较增加了一个ItemDecoration的offset。最终执行child.measure(widthSpec, heightSpec);
  • 4️⃣ layoutDecoratedWithMargins(view, left, top, right, bottom); 这个方法执行也是标准的view布局流程。使用child.layout(left, top, right, bottom)进行标准的布局排列。当然和measure的时候一样,这里需要针对ItemDecoration对上下左右进行offset的处理。

RecyclerView#onLayout

RecyclerView区别于以往的自定义View流程,在onMeasure中其实已经做了排列操作,所以RecyclerView#onLayout的整体流程也比较简单了,而且大部分也是上面onMeasure流程中说明过的方法。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    //核心在这里
    dispatchLayout();
    TraceCompat.endSection();
    //在dispatchLayoutStep1中,判断是否执行runAnimation操作需要用到判断
    mFirstLayoutComplete = true;
}

dispatchLayout

关于dispatchLayout的注释会发现传达了很重要的信息,可以帮助理解很多内容。

/**
 * Wrapper around layoutChildren() that handles animating changes caused by layout.
 * Animations work on the assumption that there are five different kinds of items
 * in play:
 * PERSISTENT: items are visible before and after layout
 * REMOVED: items were visible before layout and were removed by the app
 * ADDED: items did not exist before layout and were added by the app
 * DISAPPEARING: items exist in the data set before/after, but changed from
 * visible to non-visible in the process of layout (they were moved off
 * screen as a side-effect of other changes)
 * APPEARING: items exist in the data set before/after, but changed from
 * non-visible to visible in the process of layout (they were moved on
 * screen as a side-effect of other changes)
 * The overall approach figures out what items exist before/after layout and
 * infers one of the five above states for each of the items. Then the animations
 * are set up accordingly:
 * PERSISTENT views are animated via
 * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
 * DISAPPEARING views are animated via
 * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
 * APPEARING views are animated via
 * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
 * and changed views are animated via
 * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
 */

从这个方法注释,可以发现,这个的核心其实是帮助我们处理动画相关的,核心点已经不是单纯的布局相关了,当然动画变化也会导致对应的测量、布局动画。但是从这块瞬间就能理解到dispatchLayoutStep1中有关于为动画执行就行的一系列操作了。

void dispatchLayout() {
    //没有设置adapter,直接跳过,回顾下 onmeasure中其实也只是使用defaultMeasure计算了下
    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()) {
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    /**
     * 对itemView的测量、排列的最终步骤,主要用来保存一些动画使用的数据,
     * 并且触发动画并做必要的清理工作。
     */
    dispatchLayoutStep3();
}

dispatchLayoutStep3

private void dispatchLayoutStep3() {
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.mLayoutStep = State.STEP_START;
	/**
	 * 在dispatchLayoutState1和dispatchLayoutState2中操作之后,
	 * mRunSimpleAnimations=true,所以会执行下面的动画
	 */	
    if (mState.mRunSimpleAnimations) {
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
    		...
            ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
            if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                ...
                mViewInfoStore.addToPostLayout(holder, animationInfo);
                ...
            } else {
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }
        //Process view info lists and trigger animations
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    ...
    recoverFocusFromState();
    resetFocusInfo();
}

对于上面的代码,主要关注两个方法。mViewInfoStore.addToPostLayout(holder, animationInfo);mViewInfoStore.process(mViewInfoProcessCallback);

针对 mViewInfoStore.addToPostLayout(holder, animationInfo);方法,聪明的你应该已经发现其实是和dispatchLayoutStep1中的addToPreLayout成对使用的。而且刚好流程是pre是在真正的layoutChildren之前,而post是在layout之后,因此可以大胆猜测,其实就是分别对布局变化(包括增加删除内容变化)之前和之后的item状态进行了保存。之后使用mViewInfoStore.process执行动画。

再看 mViewInfoStore.process(mViewInfoProcessCallback)到底做了什么?

但从备注(Process view info lists and trigger animations)可以发现,可以发现是触发动画执行。

void process(ProcessCallback callback) {
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
            // Appeared then disappeared. Not useful for animations.
            callback.unused(viewHolder);
        } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
            // Set as "disappeared" by the LayoutManager (addDisappearingView)
            if (record.preInfo == null) {
                // similar to appear disappear but happened between different layout passes.
                // this can happen when the layout manager is using auto-measure
                callback.unused(viewHolder);
            } else {
                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
            }
        } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
            // Appeared in the layout but not in the adapter (e.g. entered the viewport)
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
            // Persistent in both passes. Animate persistence
            callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE) != 0) {
            // Was in pre-layout, never been added to post layout
            callback.processDisappeared(viewHolder, record.preInfo, null);
        } else if ((record.flags & FLAG_POST) != 0) {
            // Was not in pre-layout, been added to post layout
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_APPEAR) != 0) {
            // Scrap view. RecyclerView will handle removing/recycling this.
        } else if (DEBUG) {
            throw new IllegalStateException("record without any reasonable flag combination:/");
        }
        InfoRecord.recycle(record);
    }
}

从上面代码,可以发现使用record.flag判断了动画执行类型,在结合addToPostLayout方法中设置record.flags |= FLAG_POST;,刚好佐证了我们的猜测。

callback是一个接口,在recyclerView中对它进行了实现。

private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
      new ViewInfoStore.ProcessCallback() {
    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
            @Nullable ItemHolderInfo postInfo) {
        mRecycler.unscrapView(viewHolder);
        animateDisappearance(viewHolder, info, postInfo);
    }
    @Override
    public void processAppeared(ViewHolder viewHolder,
            ItemHolderInfo preInfo, ItemHolderInfo info) {
        animateAppearance(viewHolder, preInfo, info);
    }

    @Override
    public void processPersistent(ViewHolder viewHolder,
            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        viewHolder.setIsRecyclable(false);
        if (mDataSetHasChangedAfterLayout) {
            // since it was rebound, use change instead as we'll be mapping them from
            // stable ids. If stable ids were false, we would not be running any
            // animations
            if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                    postInfo)) {
                postAnimationRunner();
            }
        } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
            postAnimationRunner();
        }
    }
    @Override
    public void unused(ViewHolder viewHolder) {
        mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
    }
};

最终其实就是使用ItemAnimator进行了动画处理。

 void animateAppearance(@NonNull ViewHolder itemHolder,
         @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
     itemHolder.setIsRecyclable(false);
     if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
         postAnimationRunner();
     }
 }

当然SimpleItemAnimator作为ItemAnimator的默认实现,其内部实现也是我们熟悉的东西ValueAnimator,并没有啥黑科技,感兴趣的可以自行前往看看。

至此对于RecyclerView的测量(onMeasure)、排列(onLayout)流程分析完了。

谢谢阅读,期待您的指正交流。