Reference:
https://juejin.cn/post/6844904146684870669
https://blog.csdn.net/c10WTiybQ1Ye3/article/details/107193802
RecyclerView version
Androidx. Recyclerview: recyclerview: 1.1.0 – alpha05
Start with onLayout of RecyclerView
RecyclerView.onLayout(...) - > RecyclerView. DispatchLayout () / / dispatchLayoutStep1 method is equivalent to the pre layout; // Source code comment: here decide which animation to run, save the current View information, etc.; / / / dispatchLayoutStep2 method to deal with real layout - > RecyclerView. DispatchLayoutStep1 () - > RecyclerView. DispatchLayoutStep2 () //mLayout is an instance of LayoutManager ->mLayout.onLayoutChildren(mRecycler, mState); Linearlayoutmanager.fill () : Fill the given Layout -> LinearLayOutManager.Fill (Recycler, mLayoutState, state, false); // loop call, Each return a - > LinearLayoutManager. LayoutChunk (recycler, LayoutState) - > LinearLayoutManager. LayoutState. Next () / / ItemView through Recycler access to the specified location - > RecyclerView. Recycler. GetViewForPosition (int position) / / get the ViewHolder ItemView ViewHolder returned ->RecyclerView.tryGetViewHolderForPositionByDeadline(***)Copy the code
Get the ViewHolder process
The code shown
1 For pre-layout, try to get the ViewHolder from mChangedScrap
// 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); . }Copy the code
2 try to obtain ViewHolder from mAttachedScrap/mHiddenViews/mCachedViews
// 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); . }Copy the code
3 If a StableId is present, try to obtain a ViewHolder from mAttachedScrap using ID
// 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); . }Copy the code
3.1 If the user has a custom cache, try to fetch the ViewHolder from mViewCacheExtension. Customizations are not generally done
if (holder == null && mViewCacheExtension ! = null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view ! = null) { holder = getChildViewHolder(view); . }}Copy the code
4 Try to obtain the ViewHolder from mRecyclerPool
if (holder == null) { // fallback to pool ... holder = getRecycledViewPool().getRecycledView(type); . }Copy the code
5 Create a ViewHolder if none of the above methods are available
if (holder == null) { ... holder = mAdapter.createViewHolder(RecyclerView.this, type); . }Copy the code
About Pre-layout
When adapter calls notifyItemChanged() or notifyItemRangeChanged(), onLayoutChildren() is called twice, once for the pre-layout and once for the actual layout. By comparing the two different layouts, RecyclerView can complete the prediction animation.
Let’s say we have A,B, and C, and now A and B are displayed on the screen, and we do an operation to remove B from C. At this time, in order to better user experience, we need to add an animation where C gently enters the original position of B. If the layout is only done once, we only know the final position of C (that is, the original position of B), but we do not know the starting position of C (that is, we do not know which coordinates to start the animation from, because the sliding direction is uncertain. The sliding mode of each LayoutManager is also uncertain), and if there is pre-layout, three items ABC can be laid out during pre-layout, and then the coordinates of the beginning of the animation can be known by comparing with the actual layout, so as to start the animation
About StableID
The stableID is used to determine where to recycle the ViewHolder when LayoutManager relayouts the notifyDataSetChanged method
If StableId is not set, viewHolder is recycled into RecyclerViewPool. If StableId is set, viewHolder is recycled into Scrap
private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); . if (viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); / / recovered to RecyclerViewPool recycler. RecycleViewHolderInternal (viewHolder); } else { detachViewAt(index); // Recycle recycler into Scrap. ScrapView (view); . }}Copy the code
About the Scrap
MChangedScrap and mAttachedScrap are the first places that RecyclerView looks up ViewHolder, and are only used in the layout phase, After the layout is complete, the ViewHolder in these two places will be moved to mCachedViews or mRecyclerPool.
When LayoutManager starts the layout (pre-layout or real layout), all viewholders in the current layout are recycled into Scrap. LayoutManager then retrieves viewholders one by one, unless the View changes. Otherwise it will immediately return from the Scrap to its original position.
This is to make RecyclerView and LayoutManager better separated, LayoutManager does not need to care about which View needs to be retained and which View needs to be moved to RecyclerPool, these are the responsibilities of RecyclerView. All he had to do was grab the ViewHolder from the Scrap.
//LinearLayoutManager.onLayoutChildren() @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ... / / recovered to Scrap detachAndScrapAttachedViews (recycler); . }Copy the code
Difference between mChangedScrap and mAttachedScrap
1. Add at different times, only if Item changes (notifyItemChanged or notifyItemRangeChanged is called) And ItemAnimator call canReuseUpdatedViewHolder be added to the mAttachedScrap () returns false, or added to the mChangedScrap
CanReuseUpdateViewHolder Returns’ false ‘to indicate that a different ViewHolder is used for animation, and’ true ‘to indicate that the same ViewHolder is used for animation such as fading in and out
2. MChangedScrap will only be used for pre-layout, mAttachedScrap will be used throughout the layout
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)|| ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { ... holder.setScrapContainer(this, false); Removed and Invalid, or not updated, or ItemAnimator null, / / or ItemAnimator canReuseUpdatedViewHolder returns true / / added to the mChangedScrap mAttachedScrap. Add (holder); } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList(); } holder.setScrapContainer(this, true); // Add (holder) to mChangedScrap; }Copy the code
About mHiddenViews
Find where mHiddenViews added and find the call logic in the addAnimatingView(ViewHolder ViewHolder) method.
The comments make it clear that views are added to the animatingViews list purely for animation purposes, that they are managed separately from regular views and are not visible to LayoutManager
/**
* Adds a view to the animatingViews list.
* mAnimatingViews holds the child views that are currently being kept around
* purely for the purpose of being animated out of view. They are drawn as a regular
* part of the child list of the RecyclerView, but they are invisible to the LayoutManager
* as they are managed separately from the regular child views.
* @param viewHolder The ViewHolder to be removed
*/
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
Copy the code
Direction of performance optimization