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:
RecyclerView animation principle | change the posture to see the source code (pre – layout)
RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache
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:
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.
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 ().
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
- RecyclerView encapsulates the entry animation data in two layers, which are in order
ItemHolderInfo
andInfoRecord
They 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 oneint
Type 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. InfoRecord
Is centrally stored in a store classViewInfoStore
In the. Of all entries participating in the animationViewHolder
withInfoRecord
It’s going to be stored as key-value pairs.- RecyclerView iterates through all the key-value pairs in the store class in the third phase of the layout
InfoRecord
Determines 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.ItemAnimator
To trigger the animation. RecyclerView.ItemAnimator
After receiving the animation instructions and data, encapsulate them asMoveInfo
Different types of animations are stored in different onesMoveInfo
In 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 themMoveInfo
List for each oneMoveInfo
Build the ViewPropertyAnimator instance and start the animation.
Recommended reading
RecyclerView series article directory is as follows:
-
RecyclerView caching mechanism | how to reuse table?
-
What RecyclerView caching mechanism | recycling?
-
RecyclerView caching mechanism | recycling where?
-
RecyclerView caching mechanism | scrap the view of life cycle
-
Read the source code long knowledge better RecyclerView | click listener
-
Proxy mode application | every time for the new type RecyclerView is crazy
-
Better RecyclerView table sub control click listener
-
More efficient refresh RecyclerView | DiffUtil secondary packaging
-
Change an idea, super simple RecyclerView preloading
-
RecyclerView animation principle | change the posture to see the source code (pre – layout)
-
RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache
-
RecyclerView animation principle | how to store and use animation attribute values?
-
RecyclerView list of interview questions | scroll, how the list items are filled or recycled?
-
RecyclerView interview question | what item in the table below is recycled to the cache pool?
-
RecyclerView performance optimization | to halve load time table item (a)
-
RecyclerView performance optimization | to halve load time table item (2)
-
RecyclerView performance optimization | to halve load time table item (3)
-
How does RecyclerView roll? (a) | unlock reading source new posture
-
RecyclerView how to achieve the scrolling? (2) | Fling
-
RecyclerView Refresh list data notifyDataSetChanged() why is it expensive?