[toc]

Recommended reading

Whole RecyclerView – ItemAnimator & Adapter RecyclerView animation principle | change the posture to see the source code (pre – layout)

Before I saw my colleague added animation in RecyclerView, I wanted to have a look at how to realize RecyclerView animation when I have time. I really don’t know how to realize RecyclerView animation. Let’s take our time. Some of the attributes involved in the animation process are listed at the end of this article to help you understand.

Preliminary layout

Before we begin, let’s explain a concept pre-layout.

What is pre-layout

Pre-layout refers to a layout process performed before ItemView in RecyclerView.

The role of pre-layout

The purpose of the prelayout is to give the user a better visual experience when executing the ItemAnimator.

The difference between pre-layout and formal layout

Both the pre-layout and the formal layout process execute the same code, except that the pre-layout process produces the layout before the Execution of the ItemAnimator, whereas the formal layout produces the layout after the execution of the ItemAnimator, which is the layout seen by the end user.

When will pre-layout be performed

When the layout is finished, if a new ItemView is displayed at the end of the layout, pre-layout needs to be performed, that is, when an ItemView is deleted or updated in RecycleView, pre-layout needs to be performed. See the picture below for more clarity.

As you can see from the above figure, if a new ItemView appears at the end of the layout, the adding animation of DefaultItemAnimator (fade in) will be performed when the pre-layout is not performed. This kind of “stuck” display will not feel good to the user. Of course, pre-layout would be meaningless if there were no animation at all.

Animation execution process parsing

The animation process of RecyclerView is quite complex and involves a lot of classes. If a lot of code is pasted, it may be messy. Here, I only put the call process, supplemented by annotations, hoping to have a better reading experience.

Now to begin the parsing of the formal animation execution process, I have chosen the notifyItemRemoved() method as the entry point for the analysis.

  • RecyclerView.notifyItemRemoved()
    • AdapterDataObservable.notifyItemRangeRemoved()
      • RecyclerViewDataObserver.onItemRangeRemoved()
        • AdapterHelper.onItemRangeRemoved(): Saves the information about the view changes
        • RecyclerViewDataObserver.triggerUpdateProcessor()
          • RecyclerView.requestLayout()Update the view
  • onLayout()
    • RecyclerView.dispatchLayoutStep1(): 【 1 】
      • #.processAdapterUpdatesAndSetAnimationFlags()
        • AdapterHelper.preProcess()
          • #.applyRemove()
            • #.postponeAndUpdateViewHolders()
              • #.Callback.offsetPositionsForRemovingLaidOutOrNewView()
              • RecyclerView.offsetPositionRecordsForRemove(): [2]
        • State.mRunSimpleAnimations,State.mRunPredictiveAnimations: [4]
      • ViewInfoStore.addToPreLayout(): [5]
      • LayoutManager.onLayoutChildren(): [6]
      • ViewInfoStore.addToAppearedInPreLayoutHolders(): Record new occurrencesViewHolder
    • dispatchLayoutStep2(): 【 7 】
      • LayoutManager.onLayoutChildren(): Layout again to determine what ends up on the screenThe child View
    • dispatchLayoutStep3(): [8]
      • ViewInfoStore.addToPostLayout()
      • ViewInfoStore.process(): Perform animation
    • mFirstLayoutComplete:LayoutTo complete.

[1] : The role of dispatchLayoutStep1() is as follows:

  • Handle adapter updates;
  • Decide what animation to use;
  • Save the current view information;
  • Run the prelayout and save the layout information after the prelayout is complete.

【 2 】 offsetPositionRecordsForRemove ()

The for loop iterates through the child of RecyclerView to update the position property of the corresponding ViewHolder and adds the Viewholder. FLAG_REMOVED flag to the ViewHolder to be deleted.

Method is called at the end of Recycler. OffsetPositionRecordsForRemove () to make clear the cache.

【4】mRunSimpleAnimations, mRunPredictiveAnimations

The following two state assignments are updated here.

  • mRunSimpleAnimations: Whether to executeItemAnimator.
  • mRunPredictiveAnimations: Indicates whether to perform pre-layout.
mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator ! = null && (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && (! mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && ! mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled();Copy the code

As you can see here, mFirstLayoutComplete is a necessary and sufficient condition to determine whether to execute the pre-layout, and mFirstLayoutComplete is assigned true only after the first Layout is complete. That means RecyclerView ItemAnimator is does not support the initial animation -_ -! .

[5] ViewInfoStore. AddToPreLayout ()

Step 0: Before pre-layout, record all visible ViewHolder location information in RecyclerView in viewstoreInfo. record, and set FLAG_PRE pre-layout flag.

[6] Layout. OnLayoutChildren ()

Step 1: Perform pre-layout. Take the LinearLayoutMananger as an example:

  • LinearLayoutManager.onLayoutChildren
    • #.fill(): Fill the layout
      • #.layoutChunk(): Fills the layout block, i.eItemView.

Fill () is the method to fill the blank layout in RecyclerView and the position where the pre-layout is generated and used, as follows:

int fill(){ int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunk(recycler, state, layoutState, layoutChunkResult); layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; if (! layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList ! = null || ! state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling remainingSpace -= layoutChunkResult.mConsumed; }}}Copy the code

There are three important variables in the fill() method:

  • remainingSpace: is the range of the layout to be filled, addedViewHolderAt least fill it upremainingSpaceThe region represented.
  • LayoutChunkResult.mConsumed:mConsumedEvery time islayoutChunk()Method, representing a lineViewHolderThe area occupied. If it isGridLayoutManagerSo one row will haveSpanCountViewHolder.
  • layoutState.mOffset: indicates the area that has been filled.

So we can see that if the if block in fill() is not executed the remainingSpace is not going to change. That is, if the if chunk does not execute layoutChunk() will always be called, layoutstate.moffset will always be added, and child views will be added to RecyclerView. Until all child Views are added to RecyclerView.

The if block here limits the number of layoutChunk() calls and stops when the remainingSpace is filled. Look at this if block, which has three criteria, because we’re in the pre-layout phase, so! State.isprelayout () is false, layoutstate.mscraplist! = null, are not considered here. That is to say when layoutChunkResult mIgnoreConsumed is true, the value of the call remainingSpace will not change, that is going to perform a layoutChunk (), Add one more child View to RecylerView. The code jumps to see:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { ... // Consume the available space if the view is not removed OR changed if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; }... }Copy the code

Only in the case of ItemView to be deleted or updated to true, in fact is the ViewHolder. Flag properties for FLAG_REMOVED | FLAG_UPDATE cases. The Flag attribute of the ViewHolder to be removed was set to FLAG_REMOVED in [2].

It can be seen that the ViewHolder held by viewholder. flag == FLAG_REMOVED in the pre-layout phase was not recorded when it was added to the RecyclerView layout. That is, layoutChunk() executes again after the RecyclerView’s blank layout is filled. Added a ViewHolder to RecyclerView.

Return to the dispatchLayoutStep1() method after the pre-layout and see that the current child View is looping again if a ViewHolder has not been added to ViewInfoStore in the loop [5] ViewInfoStore and flag it FLAG_APPEAR. This ViewHolder is the ViewHolder that the new complement appears due to the notifyItemRemoved() operation.

【 7 】 dispatchLayoutStep2 ()

After a preliminary layout, will perform again LayoutManager. OnLayoutChildren () to determine the layout of final appears on the screen, this method may be executed multiple times.

[8] dispatchLayoutStep3 ()

This is the final step of the layout, which is saved in dispatchLayoutStep2() as the ViewHolder information is finalized, and then the animation is triggered and the cleanup is done.

Step 3: Traversal in dispatchLayoutStep2 () in the final to display on the screen of the ViewHolder information through ViewInfoStore. AddToPostLayout () method, which is commonly ItemHolderInfo, And set the FLAG_PRE flag and log it in ViewStoreInfo.

Step 4: Call viewinfoStore.process () and execute the corresponding callback according to Flag. The callback will call ItemAnimator to execute the animation.

Finally, there are some cleanup operations.

ItemAnimator

There are three animation-related classes:

  • ItemAnimator
  • SimpleItemAnimator
  • DefaultItemAnimator

ItemAnimator

ItemAnimator is an abstract class that defines the animation to be performed on the ItemView when the adapter data is updated.

Note: When every animateAppearance(), animateChange(), animatePersistence() and animatepattern () is called, Must have at least a matching dispatchAnimationFinished () call.

Status identification:

Flag Introduction to the
FLAG_CHANGED thisViewHolderThe correspondingItemViewWill be updated
FLAG_REMOVED thisViewHolderThe correspondingItemViewWill be deleted
FLAG_INVALIDATED Adapter#notifyDataSetChanged()Is called and thisViewHolderThe content of the identifier is invalid
FLAG_MOVED thisViewHolderThe correspondingItemViewThe location of the
FLAG_APPEARED_IN_PRE_LAYOUT whenViewHolderSet when added to a view during prelayout. It is not visible during prelayout and may be visible after the layout

Important methods:

I tried my best.

  • animateAppearance()
/ * * * @ param viewHolder perform animation viewHolder * @ param preLayoutInfo by recordPreLayoutInformation () returns. When * 1. Itemview is only added to adapter * 2. LayoutManager does not support predictive animation * 3. Can't predict the ViewHolder will be visible when * preLayoutInfo can null * @ param postLayoutInfo by recordPreLayoutInformation () returns. Not null * @return animation executes the call and returns true, Public abstract Boolean appearance (ViewHolder ViewHolder, @Nullable ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);Copy the code

The ViewHolder is called by RecyclerView when you add it to the layout.

After the completion of the animation, ItemAnimator must call dispatchAnimationFinished (), if decided not to view the animation processing, then immediately call dispatchAnimationFinished ().

  • animateChange()
/** * @param oldHolder layout before the start of the ViewHolder, oldHolder may not be equal to newHolder OldHolder may not be the same * @ newHolder param preLayoutInfo by recordPreLayoutInformation () returns. Don't empty * @ param postLayoutInfo by recordPreLayoutInformation () returns. Not null * @return animation executes the call and returns true, Public abstract Boolean animateChange(recyclerView. ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, RecyclerView.ItemAnimator.ItemHolderInfo preLayoutInfo, RecyclerView.ItemAnimator.ItemHolderInfo postLayoutInfo);Copy the code

This method is called when an ItemView that exists before or after a layout receives a call to the Adapter#notifyItemChanged() method. Adapter#notifyDataSetChanged() may also trigger this method, but if the ViewType changes when called, a new ViewHolder is created and the animateAppearance() method is called for it, The old ViewHolder will be recycled.

If this method is called as a result of the Adapter# notifyDataSetChanged() call, it is likely that the contents of the project have not really changed, but are rebound from the adapter. DefaultItemAnimator skips animating the ItemView if the ViewItem position on the screen has not changed, and developers should handle this situation and avoid creating unnecessary animations.

When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the previous presentation of the item as-is and supply a new ViewHolder for the updated presentation (see: canReuseUpdatedViewHolder(ViewHolder, List). This is useful if you don’t know the contents of the Item and would like to cross-fade the old and the new one (DefaultItemAnimator uses this technique).

When using a custom ItemAnimator, it is more efficient and elegant to reuse the ViewHolder and manually set the content animation.

When you call Adapt # notifyItemChanged, the ViewType of the ItemView may change. If the call canReuseUpdatedViewHolder () the ViewHolder Item ViewType has changed or ItemAnimator returns false, OldHolder and newHolder will be different instances of ViewHolder representing the same Item. In this case, only the new ViewHolder is visible to the LayoutManager, but RecyclerView retains the old ViewHolder for animation attachment.

Animation after execution, if oldHolder and newHolder is the same instance, will only call dispatchAnimationFinished (), but if it is not the same instance, then every ViewHolder need to call again.

  • animateDisappearance()
/** * @param viewHolder * @param preLayoutInfo * @param postLayoutInfo * @param postLayoutInfo Public Abstract Boolean animatePattern (recyclerView. ViewHolder ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preLayoutInfo, RecyclerView.ItemAnimator.ItemHolderInfo postLayoutInfo);Copy the code

Called when ViewHolder disappears from the layout. When this method is called, the corresponding View has been removed by LayoutManager, although it has not been removed by RecylerView. It may have been removed from the Adapter or not be visible due to other factors. Can check passed to recordPreLayoutInformation () flag symbol to differentiate between the two cases.

When changes and disappears occur in the same layout process, it is up to the ItemAnimator and LayoutManager to determine which animation callback to execute, depending on the source code.

After the completion of the animation, ItemAnimator must call dispatchAnimationFinished (), if decided not to view the animation processing, then immediately call dispatchAnimationFinished ().

  • animatePersistence()
/** * @param viewHolder * @param preLayoutInfo * @param postLayoutInfo * @param postLayoutInfo Public abstract Boolean animatePersistence(ViewHolder ViewHolder, ItemHolderInfo preLayoutInfo) ItemHolderInfo postLayoutInfo);Copy the code

This method is called when the Adapter#notifyItemChanged() or Adapter#notifyDataSetChanged() is not called and the ViewHolder exists before and after the layout with no change in content.

In SimpleItemAnimator, animation is performed when the position of the Item changes.

After the completion of the animation, ItemAnimator must call dispatchAnimationFinished (), if decided not to view the animation processing, then immediately call dispatchAnimationFinished ().

  • canReuseUpdatedViewHolder()
/** * @param viewHolder @param payloads @return returns true, Otherwise it returns false * / public Boolean canReuseUpdatedViewHolder (ViewHolder ViewHolder) public Boolean canReuseUpdatedViewHolder(ViewHolder viewHolder, List<Object> payloads)Copy the code

When an Item is changed, the ItemAnimator can decide whether to use the same ViewHolder repeatedly to perform the animation or whether a copy of the Item should be created, and the ItemAnimator will use both to perform the animation (for example, fade in and out).

Note that this method is called only if ViewHolder#ViewType is unchanged. Otherwise the ItemAnimator will always receive two different ViewHolder simultaneously in the animateChange() method.

If your application USES local update (payloads), then you can rewrite canReuseUpdatedViewHolder () based on payloads to judgment.

  • recordPreLayoutInformation()

RecordPreLayoutInformation () in pre_layout (layout) after the call, used to hold the ViewHolder related information. ItemHolderInfo is returned by default.

  • recordPostLayoutInformation()

RecordPostLayoutInformation () called after layout, used to hold ViewHolder final state of the relevant information. ItemHolderInfo is returned by default.

SimpleItemAnimator

SimpleItemAnimator inherits from ItemAnimator and implements the methods provided by ItemAnimator, AnimateAdd (), animateRemove(), animateChange(), and animateMove() methods are more easily understood.

DefaultItemAnimator

DefaultItemAnimator inherits from SimpleItemAnimator and implements the add, Remove, change, move methods it provides.

In general, custom animation, implement their own add, remove, change, move is good. DefaultAnimatorAnimator is a ViewPropertyAnimator. AnimateMove is an alpha animator.

Custom ItemAnimator

Because of mFirstLayoutComplete, the animation is not invoked when the RecyclerView is loaded for the first time. Typically, animation operations seen on the first load are the result of a call to execute the animation in onBindViewHolder().

Related classes

ViewHolder

ViewHolder and anyone who’s used RecyclerView knows that. It contains the corresponding ItemView and related information, such as Position and status information.

Flag Introduction to the
FLAG_BOUND thisViewHolderWith aPositionThe binding
FLAG_UPDATE thisViewHolderThe corresponding data is changed and needs to be bound again to update the data
FLAG_INVALID thisViewHolderThe corresponding data is invalid and needs to be bound with new data
FLAG_REMOVED thisViewHolderRefers to data that has been moved out of the data set, while it can still be used for events such as moving animations
FLAG_NOT_RECYCLABLE thisViewHolderThis sign should not be recycled throughsetIsRecyclable()Set to preserve the view during animation
FLAG_RETURNED_FROM_SCRAP thisViewHolderfromscrapThe list returns, which means we expect thisitemViewaddView()The call. Before being added toRecyclerViewThe layout before thisViewHolderStill inscrapList until the end of the layout if it has not been added toRecyclerViewLayout, then byRecyclerViewrecycling
FLAG_IGNORE thisViewHolderBy completelyLayoutManagerManagement. Unless the replacementLayoutManagerOtherwise, we won’t scrap it, recycle it, or delete it. It has onLayoutManagerIt’s still completely visible
FLAG_TMP_DETACHED When thisViewHolderWhen detached from the parent view, set this flag so that the correct action is taken when it needs to be removed or added
FLAG_ADAPTER_POSITION_UNKNOWN * theViewHolderThe location of the cannot be determined before binding to the new locationFLAG_INVALIDdifferent
FLAG_ADAPTER_FULLUPDATE When callingaddChangePayload(null)Set when
FLAG_MOVED * ItemAnimatorIn the changeViewHolderPosition time setting
FLAG_APPEARED_IN_PRE_LAYOUT * ItemAnimatorAppears in the pre-layoutViewHolderThe use of

ItemHolderInfo

ItemHolderInfo is the inner class of the ItemAnimator that holds the position of the ViewHolder. If you want to customize the ItemAnimator, you can override it and attach more information to it.

public int left;
public int top;
public int right;
public int bottom;
Copy the code

InfoRecord

InfoRecord is an inner class of ViewInfoStore. It records the animation to be executed by the ViewHolder, as well as the start and end information for animation execution.

There are three main variables in InfoRecord

int flags;
RecyclerView.ItemAnimator.ItemHolderInfo preInfo;
RecyclerView.ItemAnimator.ItemHolderInfo postInfo;
Copy the code
  • PreInfo (ItemHolderInfo): Before animation executionViewHolderLocation information of
  • PostInfo (ItemHolderInfo): After animation is executedViewHolderLocation information of
  • flag: according to theflagDetermine which animation to perform

ViewInfoStore

The ViewInfoStore contains infoRecords for all viewholders associated with the animation.

Contains an ArrayMap:

final SimpleArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new SimpleArrayMasp<>();
Copy the code

And provides the process() method, which is the entry point to start executing the animation. Each item of the ArrayMap is iterated through in the process() method, and the corresponding callback is executed according to InfoRecord#flag, which calls the corresponding method of ItemAnnimator to execute the animation.