How to obtain the property value of RecyclerView entry animation, and where to store it? This article continues to answer this question by walking through the source code.

According to the analysis of the last two chapters, in order to make animation RecyclerView layout, there are two times: pre-layout + post-layout, and the items before and after animation are successively filled into the list. After the entry is filled, its position relative to the upper left corner of RecyclerView is determined. How is the location information saved between the two RecyclerView layouts?

This is the third RecyclerView animation principle, a series of articles directory as follows:

  1. RecyclerView animation principle | change the posture to see the source code (pre – layout)

  2. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

  3. RecyclerView animation principle | how to store and use animation attribute values?

primers

This source code analysis is based on the following Demo scenario:

There are two entries in the list (1 and 2). If you delete 2, 3 will move smoothly from the bottom of the screen and take its place.

In order to achieve this effect, the RecyclerView strategy is: perform a pre-layout for the entry before animation, and load invisible entry 3 into the layout to form a layout snapshot (1, 2, 3). Perform a post-layout for the animated entry, again creating a layout snapshot (1, 3). Compare the position of entry 3 in the two snapshots to see how it should be animated.

Here I quote the conclusion already reached in the previous article:

  1. RecyclerView table in order to achieve the animation, the layout for a second time (after preliminary layout + layout) in the source code for LayoutManager. OnLayoutChildren () is called twice.

  2. State.mInPreLayout is used to indicate whether it is in the pre-layout phase. Preliminary layout of the life cycle begins with RecyclerView. DispatchLayoutStep1 (), finally RecyclerView. DispatchLayoutStep2 ().

  3. During the pre-layout phase, if an entry is removed, the space it occupies is ignored, and the extra space is used to load additional entries that are off-screen and would not otherwise be loaded.

The third point, in the source code, looks like this:

public class LinearLayoutManager {
    // Layout entry
    public void onLayoutChildren(a) {
        // Keep filling in the entries
        fill() {
            while(List has free space){// Populate a single entry
                layoutChunk(){
                    // make the entry a subview
                    addView(view)
                }
                if(The entry is not removed.) {Remaining space -= Occupied space of the entry}... }}}}Copy the code

This is the RecyclerView fill entry pseudo-code. In the Demo example, in the pre-layout stage, onLayoutChildren() is executed for the first time. Since entry 2 is deleted, the space it takes up will not be deducted, causing the while loop to execute one more time, so that entry 3 is filled into the list.

Save the layout animation property value

RecyclerView marks the layout phase with an Int value mLayoutStep, which has three possible values.

public class RecyclerView {
    public static class State {
        static final int STEP_START = 1;
        static final int STEP_LAYOUT = 1 << 1;
        static final int STEP_ANIMATIONS = 1 << 2; // The layout animation stage
        int mLayoutStep = STEP_START; // Current layout stage}}Copy the code

You can know when the entry animation will start by looking globally when mLayoutStep is assigned to STEP_ANIMATIONS:

public class RecyclerView {
    final State mState = new State();
    private void dispatchLayoutStep2(a) {// Layout sub-entries phase 2
        mState.mInPreLayout = false; // The pre-layout is complete
        mLayout.onLayoutChildren(mRecycler, mState); // Start layout

        mState.mLayoutStep = State.STEP_ANIMATIONS; // Mark the animation stage for the layout. }}Copy the code

After RecyclerView finishes the back layout, set mstate. mLayoutStep to state. STEP_ANIMATIONS to indicate that the entry animation is about to start.

At the beginning of the next “Layout sub-entry Phase 3”, assert:

public class RecyclerView {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {... dispatchLayout();// Start layout of RecyclerView child entries. }void dispatchLayout(a) {...if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();// Layout the first stage of subentries. dispatchLayoutStep2();// Layout sub-entries phase 2}... dispatchLayoutStep3();// Layout sub-entries phase 3
    }
    
    private void dispatchLayoutStep3(a) {
        // Assert "in the animation phase of the layout"mState.assertLayoutStep(State.STEP_ANIMATIONS); . }public static class State {
        // Declare whether mLayoutStep is accepted, otherwise throw an exception
        void assertLayoutStep(int accepted) {
            if ((accepted & mLayoutStep) == 0) {
                throw new IllegalStateException("Layout state should be one of "
                        + Integer.toBinaryString(accepted) + " but it is "+ Integer.toBinaryString(mLayoutStep)); }}}}Copy the code

Thus concluded that trigger logic of animation will appear in RecyclerView. DispatchLayoutStep3 (), continue to the next day the source code:

public class RecyclerView {
    private void dispatchLayoutStep3(a) {
            // Iterate over the entry
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                // Obtain the ViewHolder corresponding to the entry
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                // Get entry animation information
                finalItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder); . }}}Copy the code

RecyclerView iterates through all the current entries in the third stage of the layout sub-entries (for Demo scenarios, entries 1 and 3 will be iterated), Call ItemAnimator. RecordPostLayoutInformation ItemHolderInfo () to build them one by one item from the animation information:

public class RecyclerView {
    public abstract static class ItemAnimator {
        // Record the layout information
        public ItemHolderInfo recordPostLayoutInformation(State state,ViewHolder viewHolder) {
            return obtainHolderInfo().setFrom(viewHolder);
        }
        // Build the entry information
        public ItemHolderInfo obtainHolderInfo(a) {
            return new ItemHolderInfo();
        }
        // Entry information entity class
        public static class ItemHolderInfo {
            // Top, bottom, left, and right relative to the list
            public int left;
            public int top;
            public int right;
            public int bottom;
            
            public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) {
                return setFrom(holder, 0);
            }
            // Record the entry position
            public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,int flags) {
                final View view = holder.itemView;
                this.left = view.getLeft();
                this.top = view.getTop();
                this.right = view.getRight();
                this.bottom = view.getBottom();
                return this; }}}}Copy the code

The ItemHolderInfo instance is built to record the position of the entry relative to the top left of the list (top, bottom, left, and right) and then adds it to the ViewInfoStore by calling addToPostLayout() :

public class RecyclerView {
    // Used to store entry animation information
    final ViewInfoStore mViewInfoStore = new ViewInfoStore();
    private void dispatchLayoutStep3(a) {
            // Iterate over the entry
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);
                // Save the post-layout entry animation information to mViewInfoStoremViewInfoStore.addToPostLayout(holder, animationInfo); . }}}Copy the code

ViewInfoStore is used to store entry animation information:

class ViewInfoStore {
    // ArrayMap structure to store ViewHolder and its corresponding animation information
    final ArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
    // Stores the post-layout entry and its animation information
    void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            // Get the InfoRecord instance from the pool
            record = InfoRecord.obtain();
            // Bind ViewHolder to InfoRecord
            mLayoutHolderMap.put(holder, record);
        }
        record.postInfo = info; // Store the post-layout entry animation information in the postInfo field
        record.flags |= FLAG_POST; // Append FLAG_POST to the flag bit
    }
    
    static class InfoRecord {
        int flags; / / tag
        static final int FLAG_PRE = 1 << 2; / / the pre - layout markup
        static final int FLAG_POST = 1 << 3; / / post - layout markup
        static final int FLAG_APPEAR = 1 << 1; // A flag appears in the entry
        RecyclerView.ItemAnimator.ItemHolderInfo preInfo;// Pre-layout Indicates the position of the entry
        RecyclerView.ItemAnimator.ItemHolderInfo postInfo;// post-layout Indicates the position of the entry
        // Pool: to avoid memory jitter
        static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
        // Get the InfoRecord instance from the pool
        static InfoRecord obtain(a) {
            InfoRecord record = sPool.acquire();
            return record == null ? newInfoRecord() : record; }... }}Copy the code

The entry animation information is wrapped as an InfoRecord instance and a flag bit of type int identifies which layout stages the entry has gone through. If the entry animation information is added during the post-layout phase, the flag bit is appended with FLAG_POST (this flag bit is used to determine what kind of animation to animate). Finally, the entry animation information is bound to the corresponding ViewHolder and stored in the ArrayMap structure.

Thus, the following conclusions can be drawn:

RecyclerView in the third stage of the layout will iterate over all the table items filled in the layout, and build animation information instance for each table item. The instance not only saves the relative position of the table item and the list, but also records the layout stage experienced by the table item with a marker bit. The corresponding relationship between the entry and its animation information is stored in the mLayoutHolderMap structure in ViewInfoStore.

Apply this to the Demo scenario: In the third stage of the layout, the list iterates through entries 1 and 3 to build an instance of animated information for them, with the FLAG_POST flag appended to the instance bit. This information is stored in the mLayoutHolderMap structure in ViewInfoStore.

Save pre-layout animation property values

In addition to postInfo, there is a preInfo in InfoRecord that represents animated information for post-layout and pre-layout entries, respectively. Presumably there is another addToPreLayout() that corresponds to addToPostLayout() :

class ViewInfoStore {
    // Stores the pre-layout entry and its animation information
    void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info; // Store the post-layout entry animation information in the preInfo field
        record.flags |= FLAG_PRE; // Append FLAG_PRE to the flag bit}}Copy the code

AddToPreLayout () is called in the pre-layout phase:

public class RecyclerView {
    private void dispatchLayoutStep1(a) {...// Iterate over visible entries
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                finalViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); .// Build the entry animation information
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                // Save the entry animation information to mViewInfoStoremViewInfoStore.addToPreLayout(holder, animationInfo); . }.../ / layoutmLayout.onLayoutChildren(mRecycler, mState); }}Copy the code

The first phase of RecyclerView layout iterates through all the entries and builds the animation information one by one before onLayoutChildren() is executed for the first time, that is, before pre-layout. In the Demo example, before pre-layout, the animation information for entries 1 and 2 is built and the flag bit is appended with FLAG_PRE. This information is stored in the mViewInfoStore instance.

Then RecyclerView implements onLayoutChildren(), that is, pre-layout.

public class RecyclerView {
    private void dispatchLayoutStep1(a) {
            // Traverses all entries before the pre-layout
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                finalViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); .finalItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), holder.getUnmodifiedPayloads()); mViewInfoStore.addToPreLayout(holder, animationInfo); . }.../ / layout
            mLayout.onLayoutChildren(mRecycler, mState);
            // Iterate through all entries after the pre-layout
            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                finalViewHolder viewHolder = getChildViewHolderInt(child); .// If there is no corresponding ViewHolder in ViewInfoStore
                if(! mViewInfoStore.isInPreLayout(viewHolder)) { ...// Build the entry animation information
                    finalItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); .// Bind the ViewHolder entry to its animation information and save it in mViewInfoStoremViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); }}}}Copy the code

RecyclerView traverses all entries again after pre-layout. Because preliminary layout will fill the table item 3 also to the list, so table item 3 of the animation information will also be deposited in the mViewInfoStore, but call is ViewInfoStore addToAppearedInPreLayoutHolders () :

class ViewInfoStore {
    void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.flags |= FLAG_APPEAR; // Append FLAG_APPEAR to the flag bit
        record.preInfo = info; // Store the pre-layout entry animation information in the preInfo field}}Copy the code

AddToAppearedInPreLayoutHolders addToPreLayout and () () the implementation of almost the same, the only difference is that, sign a supplemental FLAG_APPEAR, used to mark the table in table 3 that will appear on the screen.

So far, the following conclusions can be drawn:

After RecyclerView went through pre-layout, post-layout and the third stage of layout, ViewInfoStore recorded the triple information of each participating animation entry: pre-layout location information + post-layout location information + experienced layout stage.

Demo, for example, table 1, 2, 3, after the preliminary layout and layout position information is recorded in ViewInfoStore, among them 1 table after the preliminary layout and layout have appeared, so the sign bit includes FLAG_PRE | FLAG_POST, InfoRecord represents this state with a new constant FLAG_PRE_AND_POST:

class ViewInfoStore {
    static class InfoRecord {
        static final int FLAG_PRE = 1 << 2;
        static final int FLAG_POST = 1 << 3;
        static final intFLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST; }}Copy the code

Entry 2 only appears in the pre-layout phase, so the flag bit contains only FLAG_PRE. After table 3 was observed after the preliminary layout and layout, so the sign bit includes FLAG_APPEAR | FLAG_POST.

Apply animation property values

public class RecyclerView {
    private void dispatchLayoutStep3(a) {
            // Layout the entries after traversal and build the animation information and store it in mViewInfoStore
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                long key = getChangedHolderKey(holder);
                final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                 mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
            // Touch publish item execution animationmViewInfoStore.process(mViewInfoProcessCallback); . }}Copy the code

Process (MViewinFoStore.process (mViewInfoProcessCallback));

class ViewInfoStore {
    void process(ProcessCallback callback) {
        // Iterate over the positions of all participating entries
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            // Get the entry ViewHolder
            final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            // Get the animation information corresponding to ViewHolder
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            // Determine the animation type based on the flag bit of the animation information to execute the corresponding ProcessCallback callback
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                callback.unused(viewHolder);
            } else if((record.flags & FLAG_DISAPPEARED) ! =0) {
                if (record.preInfo == null) {
                    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) {
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);/ / to keep
            } else if((record.flags & FLAG_PRE) ! =0) {
                callback.processDisappeared(viewHolder, record.preInfo, null); // Disappear animation
            } else if((record.flags & FLAG_POST) ! =0) {
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);// Animation appears
            } else if((record.flags & FLAG_APPEAR) ! =0) {}// Retrieve the animation information instance to the poolInfoRecord.recycle(record); }}}Copy the code

Viewinfostore.process () iterates through the mLayoutHolderMap structure that contains animation information for all table entries and determines the type of animation to execute based on the flag bit of each entry:

  • List item 1 flags for FLAG_PRE_AND_POST so will hit the callback. ProcessPersistent ().

  • The flag bit of entry 2 contains only FLAG_PRE, so (record.flags & FLAG_PRE)! = 0, the callback. ProcessDisappeared () will be hit.

  • Table contains only a sign of item 3 in FLAG_APPEAR | FLAG_POST, so (record) flags & FLAG_APPEAR_PRE_AND_POST) = = FLAG_APPEAR_PRE_AND_POST fails, (Record.flags & FLAG_POST)! = 0 is allowed, callback.processappeared () will hit.

The ProcessCallback passed as an argument to viewinfostore.process () is a predefined animation callback in RecyclerView:

class ViewInfoStore {
    // Animation callback
    interface ProcessCallback {
        // Disappear animation
        void processDisappeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo,RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
        // Animation appears
        void processAppeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo,RecyclerView.ItemAnimator.ItemHolderInfo postInfo); . }}public class RecyclerView {
    // RecyclerView animation callback by default
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
                @Override
                public void processDisappeared(ViewHolder viewHolder, ItemHolderInfo info, ItemHolderInfo postInfo) {
                    mRecycler.unscrapView(viewHolder);
                    animateDisappearance(viewHolder, info, postInfo);// Disappear animation
                }
                @Override
                public void processAppeared(ViewHolder viewHolder,ItemHolderInfo preInfo, ItemHolderInfo info) {
                    animateAppearance(viewHolder, preInfo, info);// Animation appears}... };// Entry animation executor
    ItemAnimator mItemAnimator = new DefaultItemAnimator();
    // Animation appears
    void animateAppearance(@NonNull ViewHolder itemHolder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
        itemHolder.setIsRecyclable(false);
        if(mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { postAnimationRunner(); }}// Disappear animation
    void animateDisappearance(@NonNull ViewHolder holder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
        addAnimatingView(holder);
        holder.setIsRecyclable(false);
        if(mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { postAnimationRunner(); }}}Copy the code

The code structure of RecyclerView to execute table item animation is as follows:

if (mItemAnimator.animateXXX(holder, preLayoutInfo, postLayoutInfo)) {
    postAnimationRunner();
}
Copy the code

AnimateXXX () determines whether to animate the next frame based on the return value of ItemAnimator.animatexxx ().

public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
    @Override
    public boolean animateAppearance(RecyclerView.ViewHolder viewHolder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
        // If the coordinates in the upper left corner of the table entry change in the pre-layout and post-layout, perform the displacement animation
        if(preLayoutInfo ! =null&& (preLayoutInfo.left ! = postLayoutInfo.left || preLayoutInfo.top ! = postLayoutInfo.top)) {// Perform a displacement animation, passing in the animation starting point (the upper-left corner of the pre-layout entry) and ending point (the upper-left corner of the post-layout entry)
            return animateMove(viewHolder, 
                    preLayoutInfo.left, 
                    preLayoutInfo.top,
                    postLayoutInfo.left, 
                    postLayoutInfo.top);
        } else {
            returnanimateAdd(viewHolder); }}}Copy the code

It is passed as an argument to animateMove(), which is an abstract method defined in SimpleItemAnimator. DefaultItemAnimator implements it:

public class DefaultItemAnimator extends SimpleItemAnimator {
    @Override
    public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
            int toX, int toY) {
        final View view = holder.itemView;
        fromX += (int) holder.itemView.getTranslationX();
        fromY += (int) holder.itemView.getTranslationY();
        resetAnimation(holder);
        int deltaX = toX - fromX;
        int deltaY = toY - fromY;
        if (deltaX == 0 && deltaY == 0) {
            dispatchMoveFinished(holder);
            return false;
        }
        // Entry horizontal displacement
        if(deltaX ! =0) {
            view.setTranslationX(-deltaX);
        }
        // Entry vertical displacement
        if(deltaY ! =0) {
            view.setTranslationY(-deltaY);
        }
        // Wrap the animation of the entry to move as MoveInfo and store it in the mPendingMoves list
        mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
        // indicates that the animation is performed in the next frame
        return true; }}Copy the code

If the horizontal or vertical displacement increment is not zero, wrap the animation of the entry to be moved as MoveInfo and store it in the mPendingMoves list, and return true to perform the animation in the next frame:

public class RecyclerView {  
    // Animation appears
    void animateAppearance(ViewHolder itemHolder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
        itemHolder.setIsRecyclable(false);
        if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();// Trigger animation execution}}// Throw animation execution code into animation queues within Choreographer
    void postAnimationRunner(a) {
        if(! mPostedAnimatorRunner && mIsAttached) { ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
            mPostedAnimatorRunner = true; }}// Animation executes code
    private Runnable mItemAnimatorRunner = new Runnable() {
        @Override
        public void run(a) {
            if(mItemAnimator ! =null) {
                // Perform the animation at the next frame
                mItemAnimator.runPendingAnimations();
            }
            mPostedAnimatorRunner = false; }}; }Copy the code

Choreographer triggers animation execution by throwing a Runnable into Choreographer’s animation queue, and when the next vSYNC signal arrives, Choreographer takes Runnable instances from the animation queue to execute, And threw it to the main thread execution (detailed analytical can click to read the source code about the Choreographer long knowledge | Android caton true because “frame”? . Perform the content of the defined in ItemAnimator. RunPendingAnimations () :

public class DefaultItemAnimator extends SimpleItemAnimator {
    @Override
    public void runPendingAnimations(a) {
        // If the list of displacement animations is not empty, it represents the displacement animations to be performed
        booleanmovesPending = ! mPendingMoves.isEmpty();// Whether the delete animation is to be executed
        booleanremovalsPending = ! mPendingRemovals.isEmpty(); .// Handle the displacement animation
        if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run(a) {
                    for (MoveInfo moveInfo : moves) {
                        // The displacement animation is implementedanimateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); }};// If there is a delete animation, delay the execution of the shift animation, otherwise execute immediately
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else{ mover.run(); }}... }}Copy the code

Iterate through the mPendingMoves list to build an animation by calling animateMoveImpl() for each displacement animation to be executed:

public class DefaultItemAnimator extends SimpleItemAnimator {
    void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final View view = holder.itemView;
        final int deltaX = toX - fromX;
        final int deltaY = toY - fromY;
        if(deltaX ! =0) {
            view.animate().translationX(0);
        }
        if(deltaY ! =0) {
            view.animate().translationY(0);
        }

        // Get the animation instance
        final ViewPropertyAnimator animation = view.animate();
        mMoveAnimations.add(holder);
        // Set the animation parameters and start
        animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animator) {
                dispatchMoveStarting(holder);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                if(deltaX ! =0) {
                    view.setTranslationX(0);
                }
                if(deltaY ! =0) {
                    view.setTranslationY(0); }}@Override
            public void onAnimationEnd(Animator animator) {
                animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); }}Copy the code

The original default entry animation was implemented via ViewPropertyAnimator.

conclusion

  1. RecyclerView encapsulates the entry animation data in two layers, which are in orderItemHolderInfoandInfoRecordThey record the position of the pre-layout and post-layout items of the list, that is, the position of the rectangular area of the item relative to the top left corner of the list. It also uses oneintType flag bits to record which layout stages the entry has gone through to determine the type of animation (appear, disappear, hold) the entry should do.
  2. InfoRecordIs centrally stored in a store classViewInfoStoreIn the. Of all entries participating in the animationViewHolderwithInfoRecordIt’s going to be stored as key-value pairs.
  3. RecyclerView iterates through all the key-value pairs in the store class in the third phase of the layoutInfoRecordDetermines which animation to execute based on the flag bit in. The location information of the pre-layout and post-layout of the entry is passed toRecyclerView.ItemAnimatorTo trigger the animation.
  4. RecyclerView.ItemAnimatorAfter receiving the animation instructions and data, encapsulate them asMoveInfoDifferent types of animations are stored in different onesMoveInfoIn the list. The logic for executing the animations is then thrown into Choreographer’s animation queues, and when the next vSYNC signal arrives, Choreographer pulls out of the animation queue and executes the entry animations, which iterate over all of themMoveInfoList for each oneMoveInfoBuild the ViewPropertyAnimator instance and start the animation.

Recommended reading

RecyclerView series article directory is as follows:

  1. RecyclerView caching mechanism | how to reuse table?

  2. What RecyclerView caching mechanism | recycling?

  3. RecyclerView caching mechanism | recycling where?

  4. RecyclerView caching mechanism | scrap the view of life cycle

  5. Read the source code long knowledge better RecyclerView | click listener

  6. Proxy mode application | every time for the new type RecyclerView is crazy

  7. Better RecyclerView table sub control click listener

  8. More efficient refresh RecyclerView | DiffUtil secondary packaging

  9. Change an idea, super simple RecyclerView preloading

  10. RecyclerView animation principle | change the posture to see the source code (pre – layout)

  11. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

  12. RecyclerView animation principle | how to store and use animation attribute values?

  13. RecyclerView list of interview questions | scroll, how the list items are filled or recycled?

  14. RecyclerView interview question | what item in the table below is recycled to the cache pool?

  15. RecyclerView performance optimization | to halve load time table item (a)

  16. RecyclerView performance optimization | to halve load time table item (2)

  17. RecyclerView performance optimization | to halve load time table item (3)

  18. How does RecyclerView roll? (a) | unlock reading source new posture

  19. RecyclerView how to achieve the scrolling? (2) | Fling

  20. RecyclerView Refresh list data notifyDataSetChanged() why is it expensive?