RecyclerView table item animation is how to achieve? How many times will RecyclerView do table item animation layout? What does pre-layout mean? I took so many questions in the boundless source code to search hard, this would like to give the answer directly, but found that the process is also worth recalling, and listen to me slowly.
This is the first 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
I first heard the concept of pre-layout from an interview question: why should RecyclerView pre-layout?
The following scenario uses pre-layout:
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.
How is this done? How does RecyclerView know the animation trajectory of item 3? Although the end of the animation is already there (the top of entry 2), what about the start? LayoutManager only loads all visible entries. Entry 3 is not visible and will not be layout until entry 2 is deleted.
In this case, RecyclerView’s strategy is “execute layout twice” : perform 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.
How does it work? Search the source code for the answer ~
Pre-layout life cycle
At first, I didn’t know where to start. Recyclerview.onlayout (); recyclerView.onLayout (); recyclerView.onLayout ();
Looking at the source code is like this, and sometimes it feels more like aimless shopping than a clear-headed Taobao search.
public class RecyclerView {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();// Distribute the layout
TraceCompat.endSection();
mFirstLayoutComplete = true; }}Copy the code
Recyclerview.onlayout () is very short, one can find the key dispatchLayout() :
public class RecyclerView {
void dispatchLayout(a) {
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
// Distribute layout 1
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
// Distribute layout 2
dispatchLayoutStep2();
} else if(mAdapterHelper.hasUpdates() || mLayout.getWidth() ! = getWidth() || mLayout.getHeight() ! = getHeight()) { mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
// Distribute layout 3dispatchLayoutStep3(); }}Copy the code
Layout consists of three steps, starting with step 1:
public class RecyclerView {
private void dispatchLayoutStep1(a) {... mState.mInPreLayout = mState.mRunPredictiveAnimations; . }public static class State {
boolean mInPreLayout = false; . }}Copy the code
A Boolean variable mInPreLayout was found in the first step of the distribution layout, which literally means “in the pre-layout process”.
Find a bit of pre-layout information, and the question that comes to mind is “When is mInPreLayout set to True and when is mInPreLayout set to False?” Answer this question to know the life cycle of pre-layout.
Global search mInPreLayout assigned place, in addition to mState. MInPreLayout = mState. MRunPredictiveAnimations; Everything else is set to false. Presumably mState mRunPredictiveAnimations must be true! How do you verify that? See where it is assigned:
public class RecyclerView {
private void processAdapterUpdatesAndSetAnimationFlags(a) {
...
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null&& (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && (! mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && ! mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled(); }}Copy the code
MRunPredictiveAnimations the value of mRunPredictiveAnimations is determined by N additional Boolean variables. Do I have to search the other variables one by one to determine its value? (There’s an easier way to verify this, which is discussed below.)
MRunPredictiveAnimations must have a value of true, otherwise mInPreLayout will always be false.
Look at the source code is like this, the source code is boundless, turn back, so far as it is good.
Continue to check dispatchLayoutStep1() for the rest of the code:
public class RecyclerView {
private void dispatchLayoutStep1(a) {... mState.mInPreLayout = mState.mRunPredictiveAnimations; .if(mState.mRunPredictiveAnimations) { ... mLayout.onLayoutChildren(mRecycler, mState); . }... }}Copy the code
In the discovery of a critical LayoutManager. OnLayoutChildren (), it has a long note, the effect is “the method is used for layout all the tables in the Adapter. If the entry animation is supported, onLayoutChildren() is called twice, the first time called pre-layout, which is a pre-layout before the actual layout of the entry.”
Search LayoutManager. OnLayoutChildren () is invoked, only two, one in RecyclerView. DispatchLayoutStep1 (), Another in RecyclerView. DispatchLayoutStep2 () :
public class RecyclerView {
private void dispatchLayoutStep2(a) {... mState.mInPreLayout =false;/ / end of the pre - layout
mLayout.onLayoutChildren(mRecycler, mState); // Start real layout. }}Copy the code
In the second step of the layout, set mInPreLayout to false before calling onLayoutChildren(), and the pre-Layout is finished.
And mState is passed as an argument to onLayoutChildren(), which must read mInPreLayout in onLayoutChildren().
Here, combined with comments and code walkthroughs, we can draw some conclusions:
RecyclerView table in order to achieve the animation, the layout for a second time, preliminary layout for the first time, the second is really layout, on the source of LayoutManager. OnLayoutChildren () is called twice
The value of mstate. mInPreLayout marks the life cycle of the pre-layout. Preliminary layout of the process begins with RecyclerView. DispatchLayoutStep1 (), finally RecyclerView. DispatchLayoutStep2 (). Two calls LayoutManager. OnLayoutChildren () because the flag bit different and perform different logic branch.
Prelayout populates additional entries
Knowing where the pre-layout starts and ends Narrows the scope for walk-through code. Only need to locate in LinearLayoutManager. OnLayoutChildren (), can know in advance the layout what to do.
Pre-layout must do a lot of things, but the big concern now is “how do I populate additional invisible entries during pre-layout?”
In RecyclerView cache mechanism OnLayoutChildren () tells you how to find the “fill table entry” logic step by step in the source code, which happens to be in onLayoutChildren(), quote:
public class LinearLayoutManager {
// Layout entry
public void onLayoutChildren(a) {
// Fill in the entry
fill() {
while(List has free space){// Populate a single entry
layoutChunk(){
// make the entry a subview
addView(view)
}
}
}
}
}
Copy the code
RecyclerView delegates the task of layout entries to the LinearLayoutManager. The LinearLayoutManager lays out the entries, iterating through calls to layoutChunk() in the fill() method to fill in the entries one by one until the list runs out of space.
For filling table entries, fill() and layoutChunk() are the two key methods where the logic for adding additional entries must lie:
public class LinearLayoutManager {
// Fill the entry according to the remaining space
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...// Calculate free space = free space + extra space
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
// Fill more entries when the remaining space is greater than 0
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { ... layoutChunk() ... }}}Copy the code
LinearLayoutManager
The remaining space is calculated before the loop fills the entrymExtraFillSpace
Caught my attention, it and I care about the problem “extra table entries” match, thought“It could be during pre-layoutmExtraFillSpace
Increases, relaxing the loop conditions so that additional entries are populated.So I started searching for where it was assigned, and there were 11 (a bit too many, and quite a bit of a panic) :
If you look closely, most of the assignment takes place in onLayoutChildren() :
if (mAnchorInfo.mLayoutFromEnd) {// Start the layout from the end
mLayoutState.mExtraFillSpace = extraForStart;
} else {// Start the layout from the header
mLayoutState.mExtraFillSpace = extraForEnd;
}
Copy the code
And respectively in a different direction branch, the list is only for a direction an assignment statement is executed, literally sought a mLayoutState mExtraFillSpace = extraForEnd; Continue searching for where extraForEnd is assigned:
Look at the source code is like this, thousands of roads, pick a silk.
public class LinearLayoutManager {
private int[] mReusableIntPair = new int[2];
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {... mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair); / / calculated value
int extraForEnd = Math.max(0, mReusableIntPair[1]) / / assignment}}Copy the code
ExtraForEnd values and mReusableIntPair [1], and it’s on the calculateExtraLayoutSpace () are counted, continue to jump:
If you want to know the value of one variable, you may need to know the value of N other variables.
public class LinearLayoutManager {
// Calculate extra space
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,@NonNull int[] extraLayoutSpace) {
int extraLayoutSpaceStart = 0;
int extraLayoutSpaceEnd = 0;
int extraScrollSpace = getExtraLayoutSpace(state);/ / calculated value
if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
extraLayoutSpaceStart = extraScrollSpace;
} else {
extraLayoutSpaceEnd = extraScrollSpace;
}
extraLayoutSpace[0] = extraLayoutSpaceStart;
extraLayoutSpace[1] = extraLayoutSpaceEnd;/ / assignment}}Copy the code
CalculateExtraLayoutSpace () the method name let me more firmly believe that this road right (additional table corresponds to the extra space).
This method calls getExtraLayoutSpace() again and assigns the result to extraLayoutSpace[1], continuing the jump:
Look at the source code is like this, kept jumping to jump, sometimes the long jump forgot why jump.
public class LinearLayoutManager {
protected int getExtraLayoutSpace(RecyclerView.State state) {
if (state.hasTargetScrollPosition()) {
return mOrientationHelper.getTotalSpace();
} else {
return 0; }}}Copy the code
Method returns either 0 or return mOrientationHelper. GetTotalSpace (), I prefer to believe the latter, because only returns non-zero value can be confirmed. To check, I had to do one more:
public class RecyclerView {
public static class State {
public boolean hasTargetScrollPosition(a) {
returnmTargetPosition ! = RecyclerView.NO_POSITION; }}}Copy the code
When I saw this, I was confused because the delete operation did not scroll the list, so hasTargetScrollPosition() should return false, and getExtraLayoutSpace(), which returns extra space, should return 0. I can’t accept the fact that…
Look at the source code is like this, hardships on a road for a long time, but found in the end is a dead end.
Did the list scroll?
How do I prove that I’m rolling?
Continue searching where mTargetPosition is assigned, okay?
No… I can’t jump any more.
Take a hard look at the source code in the afternoon, but also did not see the results you want, more deadly is hard to see it is easy to drill, limited life is spent in this endless details.
The fastest way to find the value of a variable is breakpoint debugging, which can also be used to read the source code. Write a simple Demo to simulate the scenario of deleting an entry and put the breakpoint on the line where the free space is calculated:
public class LinearLayoutManager {
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...// Calculate free space = existing space + extra space (breakpoint)
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { ... layoutChunk() ... }}}Copy the code
Tell me the breakpoint layoutState mExtraFillSpace 0 indeed!
Does the value of layoutState. MAvailable increase during pre-layout? Breakpoint tells me no!
Loop conditions are not relaxed! How is the extra entry populated?
I will play in the circular breakpoint condition while ((layoutState. MInfinite | | remainingSpace > 0) && layoutState. HasMore (state)), surprised to find that a new clues: In normal layout, when the second entry is filled, the remainingSpace is equal to 0, but in the same case, in pre-layout stage, the remainingSpace is not 0, so the loop can go one more time, that is, the entry 3 can be filled in.
After each loop fills the entryremainingSpace
Should be smaller. Is this step skipped when populating deleted entries?
It’s time to look hard at the source code again:
public class LinearLayoutManager {
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
// Result of filling the entry
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
// Populate a single entry (pass layoutChunkResult)layoutChunk(recycler, state, layoutState, layoutChunkResult); .if(! layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList ! =null| |! state.isPreLayout()) {// Deduct the space used to fill the entry from the remaining spaceremainingSpace -= layoutChunkResult.mConsumed; }}}}Copy the code
The only code in the loop that deducts the remaining space is wrapped in a conditional expression that does or on three conditions, one of them! State.isprelayout () must be true for the non-pre-layout phase, which deducts space consumed by all entries regardless of other conditions, while for the pre-Layout phase, deducts may be skipped when filling some entries. Which entries will be skipped?
Condition expressions have a variable layoutChunkResult. MIgnoreConsumed, literally means to ignore the consumption, and layoutChunkResult is passed as a parameter to layoutChunk () :
public class LinearLayoutManager {
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
// Get the next entry view to be populatedView view = layoutState.next(recycler); .// Get entry layout parameters
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// mIgnoreConsumed is set to true if the entry is removed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true; }... }}Copy the code
The breakpoint debugging is used to verify that it is the same as the guess:
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.
Walk up to here, although only answered two questions, one is the life cycle of the pre-layout, the second is how to fill the pre-layout of additional entries, but the length has been a little long, about “RecyclerView pre-layout other analysis” and “RecyclerView how to achieve table animation” in the next.
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?