[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 changesRecyclerViewDataObserver.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
:Layout
To 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, addedViewHolder
At least fill it upremainingSpace
The region represented.LayoutChunkResult.mConsumed
:mConsumed
Every time islayoutChunk()
Method, representing a lineViewHolder
The area occupied. If it isGridLayoutManager
So one row will haveSpanCount
个ViewHolder
.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 |
thisViewHolder The correspondingItemView Will be updated |
FLAG_REMOVED |
thisViewHolder The correspondingItemView Will be deleted |
FLAG_INVALIDATED |
Adapter#notifyDataSetChanged() Is called and thisViewHolder The content of the identifier is invalid |
FLAG_MOVED |
thisViewHolder The correspondingItemView The location of the |
FLAG_APPEARED_IN_PRE_LAYOUT |
whenViewHolder Set 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 |
thisViewHolder With aPosition The binding |
FLAG_UPDATE |
thisViewHolder The corresponding data is changed and needs to be bound again to update the data |
FLAG_INVALID |
thisViewHolder The corresponding data is invalid and needs to be bound with new data |
FLAG_REMOVED |
thisViewHolder Refers 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 |
thisViewHolder This sign should not be recycled throughsetIsRecyclable() Set to preserve the view during animation |
FLAG_RETURNED_FROM_SCRAP |
thisViewHolder fromscrap The list returns, which means we expect thisitemView 被 addView() The call. Before being added toRecyclerView The layout before thisViewHolder Still inscrap List until the end of the layout if it has not been added toRecyclerView Layout, then byRecyclerView recycling |
FLAG_IGNORE |
thisViewHolder By completelyLayoutManager Management. Unless the replacementLayoutManager Otherwise, we won’t scrap it, recycle it, or delete it. It has onLayoutManager It’s still completely visible |
FLAG_TMP_DETACHED |
When thisViewHolder When 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 |
* theViewHolder The location of the cannot be determined before binding to the new locationFLAG_INVALID different |
FLAG_ADAPTER_FULLUPDATE |
When callingaddChangePayload(null) Set when |
FLAG_MOVED |
* ItemAnimator In the changeViewHolder Position time setting |
FLAG_APPEARED_IN_PRE_LAYOUT |
* ItemAnimator Appears in the pre-layoutViewHolder The 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 executionViewHolder
Location information ofPostInfo (ItemHolderInfo)
: After animation is executedViewHolder
Location information offlag
: according to theflag
Determine 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.