Keep learning and say goodbye to job anxiety

One farewell emo tip at a time, today brings RecyclerView#LayoutManager. 😏 ~ is actually an indirect entry into the layout of the three-piece theme.

I’m sure you’re all familiar with LayoutManager. If you’re not familiar with LayoutManager or don’t know what it does, this article is probably not for you.

Because RecyclerView internal content is really more, in view of I may understand there will be some deviation, welcome to learn RecyclerView source code students point out the exchange. Manual dog head ~

Let’s take a look at what LayoutManager can do from the source code

RecycerView#setLayoutManager(@Nullable LayoutManager layout)

The method itself is an entry point to normal LayoutManager usage.

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();
}
Copy the code

The above code logically handles two main things:

  • 1, ifLayoutManagerIf you reset the new one, unbind the previous one and re-bind the new one.
  • 2, use,requestLayoutNotify the layout to execute againonMeasureonLayout.

If you are not sure why executing requestLayout will cause the parent container to call onMeasure and onLayout, check out this article

With that in mind, let’s look at the onMeasure and onLayout methods.

RecyclerView#onMeasure

This method involves a lot of very useful things, although involve a lot of methods, also very long, but it is recommended to read the whole.

Some of the core methods may be involved, but to understand the process as a whole without interruption, the code description is listed separately below.

 protected void onMeasure(int widthSpec, int heightSpec) {
     /** * If layout is not set, defaultOnMeasure is used to measure the padding value based on * widthSpec and heightSpac.  * The specific rules are: * ((paddingLeft+paddingRight) or (paddingTop + paddingBottom)) With the * (ViewCompat getMinimumWidth (this) or ViewCompat. GetMinimumHeight (this)), maximum * again between calculated value and the minimum value. UNSPECIFIED, it uses either (paddingLeft+paddingRight) * or (paddingTop + paddingBottom)), With (ViewCompat getMinimumWidth (this) or * ViewCompat getMinimumHeight (this)) maximum * /
     if (mLayout == null) {
         defaultOnMeasure(widthSpec, heightSpec);
         return;
     }
     /** * RecyclerView#isAutoMeasureEnabled this method determines whether the onMeasure calculation is handled by LayoutManager or RecyclerView itself. The default value of isAutoMeasureEnabled is false. The LinearLayoutManager returns true, and the Layout itself is responsible for the 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.
          */
         / / = = = = = = = = = = = = = = = = = = = = = this is a line = = = = = = = = = = = = = = = = = = = = = =
         /** * LayoutManabger#onMeasure; /** * LayoutManabger#onMeasure; * although theoretically can use recyclerView#defaultOnMeasure to directly replace * LayoutManager#onMeasure, but due to the implementation of some old code, reference restrictions, * can not be completely replaced. * It is not recommended to override the LayoutManager#onMeasure method if you implement LayoutManager yourself. * /	
         mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

         /** * If the current measurement mode is exactly and adapter is not set, skip the following measurement process */ this code only measures the size of recyclerView and does not measure the size of internal components */
         final boolean measureSpecModeIsExactly =
                 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
         if (measureSpecModeIsExactly || mAdapter == null) {
             return;
         }

         //mLayoutStep initial state is STEP_START
         if (mState.mLayoutStep == State.STEP_START) {
             /** * The first layout mainly involves the following operations: * 1, execute the adapter update * 2, determine which animation to execute * 3, save the current view information * 4, if necessary, perform the pre-layout operation, And save the related information * * = = = = = = = = = = = = = = = line = = = = = = = = = = = = = = = = = = = = = = = = * there were some initialization, to do judgment, whether to perform the preparation processing animation * /
             dispatchLayoutStep1();
         }
         // set dimensions in 2nd step. 
         // Pre-layout should happen with old dimensions for consistency
         // Set the height, width, and measurement mode of the previously calculated container to LayoutManager
         mLayout.setMeasureSpecs(widthSpec, heightSpec);
         mState.mIsMeasuring = true;
         /** * This method can be invoked multiple times. This method is used to measure and arrange sub-ItemViews * Here, use mLayoutChildren (mRecycler, mState) to measure the layout core; * This method needs to be implemented by LayoutManager itself. * * = = = = = = = = = = = = = = = = = = = line = = = = = = = = = = = = = = = = = = = = = = = = = * plus ca change, involves the arrangement, certainly cannot leave the measure and the method of layout. * Tracking down the main methods involved: * measureChildWithMargins (view, 0, 0) - responsible for measuring and * layoutDecoratedWithMargins (view, left, top, right, bottom) - responsible for arrangement of * /
         dispatchLayoutStep2();

         // now we can get the width and height from the children.
         // Here can be understood as a calculation of the size of the sub-layout, to determine the actual size of 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.
         /** * Determine whether you need to calculate twice, the default is false, the implementation class of the LayoutManager is specific to handle, * in the LinearLayoutManager, the judgment is only in the width and height, * There is also a value whose measurement mode is not Exactly (i.e. not Exactly), and the following secondary measurement is required */
         if (mLayout.shouldMeasureTwice()) {
             MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec
             mLayout.setMeasureSpecs(
                     MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
             mState.mIsMeasuring = true;
             // Execute the itemView measurement arrangement above, as described above
             dispatchLayoutStep2();
             // now we can get the width and height from the children.
             // Re-determine the actual size of recyclerView by calculating the size of the sub-layoutmLayout.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}}Copy the code

dispatchLayoutStep1anddispatchLayoutStep2As well asdispatchLayoutStep3

There’s a code in recyclerView#onMeasure and if you look at the dispatchLayout method you’ll see something similar. Just add a dispatchLayoutStep3 method to dispatchLayout.

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

In the onMeasure above, we explained that dispatchLayoutStep1 handles some state and information initialization, as well as preparation for normal load animations and preload view animations. DispatchLayoutStep2 deals with the measurement and arrangement of real sub-views. The specific implementation will be described in detail below.

Before we get to the point, there are two more important classes that need to be explained:

  • RecyclerView#State

The recyclerView class contains useful information about the current recyclerView, such as the target scroll position or view data to get focus. Normally, recyclerView components need to pass information between each other. In order to provide a well-defined data bus between components, RecyclerView will pass the same state object to the callbacks of components that can use it to exchange data. STEP_START (initialization state), STEP_LAYOUT (change to this state after dispatchLayoutStep1), STEP_ANIMATIONS (change to this state after dispatchLayoutStep2).

  • ViewInfoStore

This class is a helper class used to track the view while performing the animation, in other words, to help the view move. In ViewInfoStore, view data and animationInfo are saved separately in different states. Used in dispatchLayoutStep1 phase, mViewInfoStore. AddToPreLayout (holder, animationInfo); You can see that preLayout is saved here. And use in dispatchLayoutStep3 mViewInfoStore. AddToPostLayout (holder, animationInfo); Save it. After all operations are completed, mVieWinFoStore.process (mViewInfoProcessCallback) is used; Perform the animation.

dispatchLayoutStep1
private void dispatchLayoutStep1(a) {
    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) {
        Return McAllback.getchildcount () {* return McAllback.getchildcount () -mhiddenviews.size (); *} * /
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
        	// Find Viewholder by childView
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if(holder.shouldIgnore() || (holder.isInvalid() && ! mAdapter.hasStableIds())) {continue;
            }
            / * * * recordPreLayoutInformation method before layout, by recyclerView calls. * The Item animator saves the view's information before the view may become rebound, moved, or removed. * The ItemHolderInfo information is passed back to the animate method after the Layout is complete. * /
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            // The addToPreLayout method is used to track information about payLayout items
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if(mState.mTrackOldChangeHolders && holder.isUpdated() && ! holder.isRemoved() && ! holder.shouldIgnore() && ! holder.isInvalid()) {longkey = 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;
}
Copy the code

In addition to some state initialization and saving of state and data, much of the code above is in the two if judgments. if (mState.mRunSimpleAnimations) {… {} and the if (mState mRunPredictiveAnimations)… }. However, through the breakpoint and reading the code, it is found that both states are false when onMeasure is implemented, so the code block will not be executed.

The dispatchLayoutStep1 method will continue to be called within the dispatchLayout method. For changes in the status of mRunSimpleAnimations and mRunPredictiveAnimations, Is also in dispatchLayoutStep1 processAdapterUpdatesAndSetAnimationFlags before execution state judgment method is performed.

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

MItemAnimator defaults to using the SimpleItemAnimator class, and the mFirstLayoutComplete setting true is set after onLayout’s dispatchLayout execution. Therefore, it is a bold guess that mRunSimpleAnimations and mRunPredictiveAnimations in DispatchLayoutP1 will only perform the actions in if animations when items are deleted, added, and changed.

A quick summary of the dispatchLayout1 method shows that this method is mainly used to initialize and save some state and item information, so that the preloaded layout can perform animation when the item is deleted, added, or changed.

dispatchLayoutStep2
private void dispatchLayoutStep2(a) { startInterceptRequestLayout(); . mLayout.onLayoutChildren(mRecycler, mState); .// Set the dynamic execution state of mStatemState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator ! =null;
    // Set the layoutStep statemState.mLayoutStep = State.STEP_ANIMATIONS; . stopInterceptRequestLayout(false);
}
Copy the code

The rest of the code is omitted, and we are concerned only with the core code implementation.

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

This method is not implemented by default, but requires the LayoutManager implementation to override. So let’s start with the LinearLayoutManager.

LinearLayoutManager#onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...// Perform cleanup
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd) {
        ...
        fill(recycler, mLayoutState, state, false); . }else{... fill(recycler, mLayoutState, state,false); . }...// Reconfigure the layout for the preloaded animation - internally still do filllayoutForPredictiveAnimations(recycler, state, startOffset, endOffset); . }Copy the code

Discover that the core is the fill method and follow up

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;
}
Copy the code

Continue to… layoutChunk

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {.../ * * * here a digression, if custom LayoutManager, * must achieve LayoutManager generateDefaultLayoutParams method. * /
    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) {
        // Omit some code... Mainly used for RTL, mLayoutDirection and other processing.
    } else {
        // Omit some code... Mainly used for RTL, mLayoutDirection and other processing.
    }
    / / 4 ️ ⃣
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}
Copy the code

In the code above, I have numbered four of the more important methods, which ARE described below.

  • 1 ️ ⃣ addView (view);

Bbsellers don’t bbsellers with the third parameter bbsellers with the price of false. This parameter determines whether the animation is executed when onLayoutChilden is called. It only works if it’s been gone or removed before. The final animator class is actually ItemAnimator.

  • 2 ️ ⃣ addDisappearingView (view);

This is similar to addView execution above, except with the BGF parameter true, the animation will be executed.

  • 3 ️ ⃣ measureChildWithMargins (view, 0, 0);

This method implements a standard view measurement process and adds an ItemDecoration offset compared with the traditional process. Finally execute child.measure(widthSpec, heightSpec);

  • 4 ️ ⃣ layoutDecoratedWithMargins (view, left, top, right, bottom);

This method also performs the standard View layout flow. Use child.layout(left, top, right, bottom) for standard layout arrangement. Of course, it is the same as in measure, the upper, lower, left and right sides need to be offset based on ItemDecoration.

RecyclerView#onLayout

RecyclerView is different from the previous custom View process, in fact, the arrangement operation has been done in onMeasure, so the overall process of RecyclerView#onLayout is relatively simple, and most of it is the method described in the above onMeasure process.

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    // The core is here
    dispatchLayout();
    TraceCompat.endSection();
    // In dispatchLayoutStep1, you need to decide whether to execute runAnimation
    mFirstLayoutComplete = true;
}
Copy the code

dispatchLayout

Comments about dispatchLayout will find that they convey important information and help you understand a lot.

/**
 * 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)}.
 */
Copy the code

From this method note, we can find that the core of this method is actually to help us deal with animation related, the core point is not only layout related, of course, animation changes will also lead to the corresponding measurement, layout animation. But from this moment you can understand that dispatchLayoutStep1 has a series of actions that are done for the animation.

void dispatchLayout(a) {
    // If there is no adapter set, just skip it and recall that the onMeasure is only computed using defaultMeasure
    if (mAdapter == null) {
        Log.e(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    / / same as above
    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);
    }
    /** * The final step in the measurement and arrangement of the itemView, mainly used to save some animation data, * and trigger the animation to do the necessary cleaning. * /
    dispatchLayoutStep3();
}
Copy the code

dispatchLayoutStep3

private void dispatchLayoutStep3(a) {
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.mLayoutStep = State.STEP_START;
	/** ** mRunSimpleAnimations=true after actions in dispatchLayoutState1 and dispatchLayoutState2, the following animations will be performed */	
    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 animationsmViewInfoStore.process(mViewInfoProcessCallback); }... recoverFocusFromState(); resetFocusInfo(); }Copy the code

For the above code, I focus on two methods. mViewInfoStore.addToPostLayout(holder, animationInfo); And mViewInfoStore. Process (mViewInfoProcessCallback); .

For mViewInfoStore. AddToPostLayout (holder, animationInfo); Method, which you should be smart enough to see is actually paired with addToPreLayout in dispatchLayoutStep1. And it happened that pre was before real layoutChildren, while Post was after Layout, so we can make a bold guess that the item state before and after layout changes (including addition, deletion and content change) was saved respectively. Then use mViewinfoStore.process to execute the animation.

What does mViewinfoStore.process (mViewInfoProcessCallback) do?

But from the notes (Process View Info Lists and trigger animations), you can see that animations are triggered to execute.

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); }}Copy the code

From the above code can be found using record. The flag to judge the animation executive type, in combination with addToPostLayout methods set in the record. The flags | = FLAG_POST; “Just confirmed our guess.

And callback is an interface, recyclerView in its implementation.

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); }};Copy the code

The end result is actually animating using the ItemAnimator.

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

ValueAnimator (SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator, SimpleItemAnimator

So far for RecyclerView measurement (onMeasure), arrangement (onLayout) process analysis is finished.

Thank you for reading and I look forward to your comments.