This article is the third article of RecyclerView complete analysis series, the content is closely followed by the previous two :RecyclerView basic design structure and RecyclerView refresh mechanism.
According to the previous analysis, LayoutManager can request a ViewHolder from Recycler when LayoutManager distributes child Views. But the only way to get a ViewHolder from Recycler is to have a ViewHolder in Recycler. How can There be ViewHolder in Recycler? This paper will analyze two issues:
RecyclerView
theView
When did you put it inRecycler
In the. And in theRecycler
How is it preserved in.LayoutManager
In theRecycler
To obtainViewHolder
When,Recycler
Looking forViewHolder
What is the logic of.
Namely when to put, how to put and when to take, how to take the problem. It’s already obvious when to recycle :LayoutManager retrieverrecycler child Views when LayoutManager arranges child views. Therefore, this paper is to clarify the other three issues. Before continuing, please know that the base unit for Recycler management is the ViewHolder and the base unit for LayoutManager operation is View, ViewHolder itemView. This paper will not analyze the RecyclerView animation view reuse logic.
To make the next part of the Recycler easier to understand, let’s review the Recycler structure:
mChangedScrap
: For preservationRecyclerView
While animating, detach’sViewHolder
.mAttachedScrap
: For preservationRecyclerView
Do data refresh (notify
) by DetachViewHolder
mCacheViews
:Recycler
The level ofViewHolder
The cache.RecyclerViewPool
:mCacheViews
When the set is full, it’s going to go here.
Let’s see how to take a ViewHolder from Recycler.
Get a ViewHolder logic from Recycler
LayoutManager calls the Recycler. GetViewForPosition (pos) to obtain a specified position (this position is a View layout position) of the View. GetViewForPosition () will call tryGetViewHolderForPositionByDeadline (position…). This method is the core method to get a View from Recycler. It is the logic of how to get a ViewHolder from Recycler, that is, the method is too long, I made a lot of cropping:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
...
if(mState. IsPreLayout ()) {/ / animation related holder = getChangedScrapViewForPosition (position); // From the cache? It shouldn't be caching, right? fromScrapOrHiddenOrCache = holder ! = null; } // 1) Find by position from scrap/hidden list/cacheif(holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); // From attach and mCacheViewsif(holder ! = null) { ... // Check whether the holder is available}}if (holder == null) {
...
final int type= mAdapter.getItemViewType(offsetPosition); // Get the type of data for this position. // 2) Find from scrap/cache via stable ids,if exists
if(madapter.hasstableids ()) {// Stable id identifies the uniqueness of a viewholder, Even if it do animation changed position holder = getScrapOrCachedViewForId (mAdapter. GetItemId (offsetPosition), // Obtain from Scrap and mCacheViews according to stable IDtype, dryRun); . }if(holder == null && mViewCacheExtension ! = null) {/ / from a user-defined cache in the collection of final View View. = mViewCacheExtension getViewForPositionAndType (this position,type); / / if you return the View RecyclerView LayoutParams propertiesif(view ! = null) { holder = getChildViewHolder(view); // Wrap it as a ViewHolder... }}ifGetRecycledViewPool ().getRecycleDView ();type); . }if(holder == null) { ... / / don't really have creates holder. = mAdapter createViewHolder (RecyclerView. This,type); . }}... boolean bound =false;
if(mstate.ispreLayout () && holder.isbound ()) {// Animation does not want to call onBindData... }else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { ... final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); / / callbindData method} final ViewGroup. LayoutParams lp = holder. ItemView. GetLayoutParams (); final LayoutParams rvLayoutParams; . Adjust the LayoutParamsreturn holder;
}
Copy the code
That is, the general steps are:
- If it’s executed
RecyclerView
Animation, tryAccording to the position
fromMChangedScrap collection
In search of aViewHolder
- try
According to the position
fromScrap collection
,Hide view collection
,MCacheViews (Level 1 Cache)
In search of aViewHolder
- According to the
LayoutManager
theposition
Update to the correspondingAdapter
theposition
. (the twoposition
In most cases it’s the same, but inChild view deleted or moved
There may be no corresponding situation) - According to the
Adapter position
, the callAdapter.getItemViewType()
In order to getViewType
- According to the
Stable ID (used to indicate the uniqueness of a ViewHolder, even if the position changes)
fromScrap collection
andMCacheViews (Level 1 Cache)
In search of aViewHolder
- According to the
The position and viewType
Try to customize from the usermViewCacheExtension
Get aViewHolder
- According to the
ViewType
Try fromRecyclerViewPool
Get aViewHolder
- call
mAdapter.createViewHolder()
To create aViewHolder
- Call if needed
mAdapter.bindViewHolder
To set theViewHolder
. - Adjust the
ViewHolder.itemview
The layout parameter of isRecycler.LayoutPrams
And returns to Holder
Although there are many steps, the logic is simple: grab a ViewHolder from several cache collections and create one if none exists. What may be confusing is when a ViewHolder is stored in the ViewHolder cache set above. Next, take a few RecyclerView scenarios to figure out the ViewHolder cache collection bit by bit.
Case one: From nothing to something
That is, there is no data in RecyclerView at the beginning. After adding the data source, adapter.notifyXXX. The state changes are shown as follows:
It is obvious that there are no ViewHolder reusable in this case Recycler. So all ViewHolder are newly created. That is, adapter.createViewholder () and adapter.bindViewholder () are called. Will those ViewHolder that you created be cached?
The ViewHolder will not be cached. In this case, Recycler only creates Viewholders through the Adapter and does not cache them
Case 2: Refresh the whole data with the original data
It is the following state:
This is essentially a drop-down refresh of the user’s feed. The pseudo-code in the implementation is as follows:
dataSource.clear()
dataSource.addAll(newList)
adapter.notifyDatasetChanged()
Copy the code
In this case, if Recycler must recycle old cards (cards of the same type), then the question is: where is the old ViewHolder stored when the user refreshes? How to call adapter.bindViewholder () to reset data?
Actually in the previous article Recycler refresh mechanism, LinearLayoutManager will be good in determining the layout of the anchor point View on the current attach RecyclerView child View are set to scrap status:
void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ... onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); / / RecyclerView specified anchor point, need to prepare a formal layout detachAndScrapAttachedViews (recycler); // Set all views to SCRAP at the beginning of the layout... }Copy the code
What is scrap status? As explained in the previous article, the ViewHolder is marked as FLAG_TMP_DETACHED and the parent of its ItemView is set to NULL.
DetachAndScrapAttachedViews is to save all the view to the Recycler mAttachedScrap collection:
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
for(int i = getChildCount() - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } } private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); . Delete some judgment logic detachViewAt(index); // Set RecyclerView's parent to NULL and flag ViewHolder as FLAG_TMP_DETACHED recycler.scrapView(view); // Add to the mAttachedScrap collection... }Copy the code
So in this caseLinearLayoutManager
On real displayThe child View
Before, I would have put allThe old View
Save in order toRecycler
theMAttachedScrap collection
In the
Next, look at how the LinearLayoutManager reuses ViewHolder from the mAttachedScrap collection during layout.
It has already been said LinearLayoutManager will the current layout child View of location to the Recycler to a child View, the call to tryGetViewHolderForPositionByDeadline (position..) . We have listed the logic of this method above, actually in the second step above:
tryAccording to the position
fromScrap collection
,Hide view collection
,MCacheViews (Level 1 Cache)
In search of aViewHolder
A ViewHolder can be obtained from the mAttachedScrap:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if(! holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && ! holder.isInvalid() && (mState.mInPreLayout || ! holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);returnholder; }}... }Copy the code
That is, if the holder in the mAttachedScrap is equal to the input position, and the holder is valid, the holder can be reused. To sum up, in case 2, almost all ViewHolder are mAttachedScrap recyclers. And there is no ViewHolder reusable in Recycler after redistribution.
Case three: Rolling reuse
This case analysis is the reuse of ViewHolder when sliding down and the preservation of ViewHolder when Recycler is present on the basis of case 2, as shown below:
In this case, the View rolled off the screen will be saved to mCacheViews first, and if mCacheViews is full, it will be saved to RecyclerViewPool.
As analyzed in the previous article in RecyclerView refresh mechanism, the RecyclerView will call the linearLayOutManager.fill () method when sliding to fill the RecyclerView sub-view according to the rolling distance. There is a method that recycles the View that scrolls off the screen after filling the child View:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) { ... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; .while((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { ... layoutChunk(recycler, state, layoutState, layoutChunkResult); // Populate a child Viewif(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if(layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } recycleByLayoutState(recycler, layoutState); // Retrieve View}}}Copy the code
Namely the fill every filling a child View call recycleByLayoutState () to recycle an old View, this method will be called to the Recycler after layers of call. RecycleViewHolderInternal (). This method is the core method for ViewHolder collection, but the logic is simple:
- check
MCacheViews collection
Is there any space left in, and if there is, put it directly intoMCacheViews collection
- If not, then
MCacheViews collection
The first one in theViewHolder
Take it out put it inRecyclerViewPool
And then put the latest ViewHolder into theMCacheViews collection
- If not successfully cached
MCacheViews collection
In, just put it inRecyclerViewPool
Why is the mCacheViews collection cached like this? Take a look at the chart below:
I think so, as shown in the figure above, if you slide up a certain distance, the ViewHolder being slid out will be cached in the mCacheViews collection, and its position will be recorded. If users can’t recycle now, see the logic of retrieving ViewHolder from Recycler:
- First by position from
MCacheViews collection
To derive - In accordance with the
viewType
fromMCacheViews collection
To derive
For the above two steps of the mCacheViews collection, the first step has already hit the ViewHolder of the cache. And there is no need to call adapter.bindViewholder (). It’s very efficient.
So in the ordinary case of rolling multiplexing,ViewHolder
The reuse of is mainly fromMCacheViews collection
And the oldViewHolder
Will be put intoMCacheViews collection
.MCacheViews collection
Squeezed out older onesViewHolder
On theRecyclerViewPool
In the
So far the basic reuse case is covered, and the rest involves RecyclerView animation. These points will continue in the next article.
Welcome to my Android advancement plan. See more dry goods.