preface

RecyclerView is an easy to use and complex control, which is distinguished from traditional ListView by its high degree of function decoupling, normalized ViewHolder writing, and friendly support for animation.

It has several modules:

  • LayoutManager: Controls the layout of items
  • RecyclerView.Adapter: Provides data for RecyclerView
  • ItemDecoration: add a separation line for RecyclerView
  • ItemAnimator: Controls the animation of an item
  • Recycler: Responsible for recycling and providing View and RecyclerView

The following is the analysis of RecyclerView from the perspective of source code (API 28). The source code of RecyclerView is very complex and difficult to finish in an article, so I plan to divide it into several parts. This article is the first one, and will analyze RecyclerView internally.

RecyclerView.Recycler

Let’s take a look at what it does. The source code reads:

A Recycler is responsible for managing scrapped or detached item views for reuse.

This means Recycler is responsible for managing views of recycled or detached items for recycling.

It has the following member variables:

Primary member variable


    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;
Copy the code

All of these variables are related to the RecyclerView cache and can be divided as follows:

Level 1 cache: mAttachedScrap, mChangedScrap

Level 2 cache: mCachedViews

Third level of cache: ViewCacheExtension

Level 4 cache: RecycledViewPool

MAttachedScrap, mChangedScrap, mCachedViews which ViewHolder are stored.

RecycledViewPool and ViewCacheExtension classes

RecycledViewPool

Continue with the official notes:

RecycledViewPool lets you share Views between multiple RecyclerViews.

RecycledViewPool Is used to share views among multiple RecyclerViews.

In use, just create RecycledViewPool instance, and then call the setRecycledViewPool(RecycledViewPool) method of RecyclerView.

RecycledViewPool To store Recycler, to access Recycler.

Member variables

RecycledViewPool has one important member variable:

    // SparseArray is similar to a HashMap whose key is int
    SparseArray<ScrapData> mScrap = new SparseArray<>();
Copy the code

ScrapData ScrapData

    static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;  / / 5
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
Copy the code

MScrap is a mapping of <int, ScrapData>, where int represents the viewType and ScrapData stores a ViewHolder collection.

The main method

getScrapDataForType

    private ScrapData getScrapDataForType(int viewType) {
        ScrapData scrapData = mScrap.get(viewType);
        if (scrapData == null) {
            scrapData = new ScrapData();
            mScrap.put(viewType, scrapData);
        }
        return scrapData;
    }
Copy the code

The method fetches ScrapData based on the viewType, and creates a new ScrapData and binds it to the viewType if it doesn’t already have a ScrapData binding.

setMaxRecycledViews

    public void setMaxRecycledViews(int viewType, int max) {
        ScrapData scrapData = getScrapDataForType(viewType);
        scrapData.mMaxScrap = max;
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        // Delete from the back until the new capacity is met
        while (scrapHeap.size() > max) {
            scrapHeap.remove(scrapHeap.size() - 1); }}Copy the code

This method can set the capacity of the corresponding viewType. If the capacity is exceeded, delete the View from the back until the new capacity is met.

getRecycledView

    public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);
        if(scrapData ! =null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            return scrapHeap.remove(scrapHeap.size() - 1);
        }
        return null;
    }
Copy the code

This method retrieves a ViewHolder based on viewType. The ViewHolder will be removed to make the Scrap heap. Returns null if not obtained.

putRecycledView

    public void putRecycledView(ViewHolder scrap) {
        final int viewType = scrap.getItemViewType();
        final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
        // The capacity is full and will not be added
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
            return;
        }
        // reset ViewHolder, such as clearing flags
        scrap.resetInternal();
        // Add the ViewHolder to the collection of the corresponding viewType and cache it
        scrapHeap.add(scrap);
    }
Copy the code

This method is also easy to understand, according to ViewHolder viewType into the corresponding collection of RecycledViewPool, if the collection is full, do not add.

Let’s look at another class:

ViewCacheExtension

ViewCacheExtension is a developer-controlled View cache helper class defined as follows:

    public abstract static class ViewCacheExtension {

        /** * Returns a View that can be binded to the given Adapter position. */
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }
Copy the code

Developers can implement this abstract class to store ViewCacheExtension in Recycler by invoking the setViewCacheExtension method of RecyclerView.

If the Recycler’s getViewForPosition method is called and neither the attached Scrap nor the Recycler’s cached View is found, Will call ViewCacheExtension getViewForPositionAndType method to get the View.

Please note that Recycler does not cache this class and whether or not we need to cache views is up to the developer.

The main method

Having seen these two classes, now go back to Recycler and look at the main ways to recycle Rcycler:

getViewForPosition

GetViewForPosition getViewForPosition getViewForPosition getViewForPosition getViewForPosition getViewForPosition

    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    
    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
Copy the code

Continue to see tryGetViewHolderForPositionByDeadline method, this method will, in turn, obtained from several cache, take a look at:

        // dispatchLayoutStep1 (dispatchLayoutStep1)
        < span style = "max-width: 100%; clear: both; min-height: 1pt;
	if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null;
	}
Copy the code

First, get it from mChangedScrap, return null if you can’t get it.

If holder is still null, execute the following code:

	// 1) Find by position from scrap/hidden list/cache
	if (holder == null) {
		holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
		if(holder ! =null) {
			if(! validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be used
			    // Retrieve invalid ViewHolder
			    // ...
			} else {
				fromScrapOrHiddenOrCache = true; }}}Copy the code

Second, obtain the ViewHolder cached from mAttachedScrap, mHiddenViews (stored in ChildHelper), and mCachedViews, respectively, based on position.

If the cache is available from mHiddenViews, remove it from mHiddenViews and add it to the Scrap cache (mAttachedScrap or mChangedScrap, as appropriate). If the cache is available from mCacheViews, remove it from mCacheViews.

If the obtained ViewHolder is invalid, it will be cleaned and recycled (put into mCachedViews or RecycledViewPool).

If no, continue:

        // Returns false by default, which can be set using the adapter.sethasstableids method
	if (mAdapter.hasStableIds()) {
		// Get cache from mAttachedScrap and mCachedViews according to ID
		holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
				type, dryRun);
		if(holder ! =null) {
			holder.mPosition = offsetPosition;
			fromScrapOrHiddenOrCache = true; }}Copy the code

Step 3: Get the cache from mAttachedScrap and mCachedViews according to id.

	// If the user has set ViewCacheExtension
	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

Step 4: Retrieve the cache from the user-set ViewCacheExtension, and continue if you don’t:

	if (holder == null) { // fallback to pool
		holder = getRecycledViewPool().getRecycledView(type);
                // ...
	}
Copy the code

Step 5, obtain cache from RecycledViewPool according to viewType.

RecycledViewPool is the last level of the cache to be RecycledViewPool. If it is not available, create a ViewHolder using the createViewHolder method of the Adapter.

	if (holder == null) {

		holder = mAdapter.createViewHolder(RecyclerView.this, type);

                // ...
	}
Copy the code

To summarize the process of getting a View at a location:

  1. Retrieves cache from mChangedScrap by position or ID
  2. Cache is obtained from mAttachedScrap, mHiddenViews (stored in ChildHelper), and mCachedViews based on position
  3. Cache is obtained from mAttachedScrap and mCachedViews according to ID
  4. Get the cache from the user-set ViewCacheExtension
  5. Get the stale ViewHolder cached from RecycledViewPool
  6. Create a ViewHolder using the createViewHolder method of the Adapter

In addition, it is not enough to have the view, but also to set the data for the view, so there is this code:

	// ViewHolder has not yet been bound and is FLAG_UPDATE or FLAG_INVALID
	else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {// ...
        
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
Copy the code

TryBindViewHolderByDeadline method will finally calling Adapter onBindViewHolder method, we usually set is on the way to view data.

recycleView

Now that we can recycle Recycler, we have to do it by recycleView. Here’s how to do it:

    public void recycleView(@NonNull View view) {
        ViewHolder holder = getChildViewHolderInt(view);
        // ...
        recycleViewHolderInternal(holder);
    }
Copy the code

Continue to see recycleViewHolderInternal:

    void recycleViewHolderInternal(ViewHolder holder) {
	// ...

        if (forceRecycle || holder.isRecyclable()) {
            if (mViewCacheMax > 0
                    && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_REMOVED
                    | ViewHolder.FLAG_UPDATE
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
						
                int cachedViewSize = mCachedViews.size();
		// If CacheViews reaches its maximum capacity (2), remove the oldest cache from CacheViews and add it to RecycledViewPool
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }

		// ...
		
		// Cache the View in mCachedViews
                mCachedViews.add(targetCacheIndex, holder);
                cached = true;
            }
            if(! cached) {// Add to RecycledViewPool if not added to mCachedViews
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true; }}// ...
    }
Copy the code

As you can see, there are two main layers of cache involved in the recycle process. The first layer is CacheViews. When adding, if the original CacheViews has reached its maximum capacity, the oldest cache is removed from CacheViews and added to the RecycledViewPool. The second layer cache is RecycledViewPool. If it cannot be added to mCacheViews, it will be added to RecycledViewPool.

supplement

Where does the View in mChangedScrap and mAttachedScrap come from

RecycleView recycleView can be used to recycle mCahceViews or recycledViewPool. So when were the views in the other two Scrap caches (mChangedScrap and mAttachedScrap) added?

Whether it’s mAttachedScrap or mChangedScrap, there’s only one way to get Recycler’s scrapView. Take a look at the method:

Recycler#scrapView

    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
		
	// If one of these conditions is met, you can enter the if loop and have a chance to cache the View to mAttachedScrap
	// 1. ViewHolder sets FLAG_REMOVED or FLAG_INVALID
	// 2. ViewHolder does not set FLAG_UPDATE
	// 3. No animation is set or the animation can reuse the ViewHolder
        if(holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {if(holder.isInvalid() && ! holder.isRemoved() && ! mAdapter.hasStableIds()) {throw new IllegalArgumentException("Called scrap view with an invalid view."
                        + " Invalid views cannot be reused from scrap, they should rebound from"
                        + " recycler pool." + exceptionLabel());
            }
    	    // Bind Recycler to ViewHolder
            holder.setScrapContainer(this.false);
            mAttachedScrap.add(holder);
        } 
	// If none of the above conditions is met, cache the View into mChangedScrap
	else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this.true); mChangedScrap.add(holder); }}Copy the code

This method determines whether to cache the View to mAttachedScrap or mChangedScrap by determining the ViewHolder flag and whether to set ItemAnimator etc.

So when does this method get called? There are two cases:

  1. So the LinearLayoutManager, for example, in its onLayoutChildren method, will call
    detachAndScrapAttachedViews(recycler);
Copy the code

This method is defined in RecyclerView’s LayoutManager, which continues to call RecycleView’s scrapOrRecycleView method or recycleView’s scrapView method if the recycleView method is recyclable.

  1. The scrapView method is also invoked when the cache is retrieved from mHiddenViews.

Scenario analysis

Here are some scenarios for Recycler to recycle.

The first layout

Since the layout process is not specifically analyzed here, we will not start with onLayout. The intermediate process is omitted, and it will eventually call onLayoutChildren of LayoutManager. Here we take the LinearLayoutManager as an example:

LinearLayoutManager#onLayoutChildren

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // ...

        // Find the anchor point.
		
	/ / (1)
	detachAndScrapAttachedViews(recycler);
		
        if (mAnchorInfo.mLayoutFromEnd) {
	    // ...
        } else {
            
            / / (2)
            fill(recycler, mLayoutState, state, false);

	    // ...
        }
		
	    // ...
    }
Copy the code

See (1), above all detachAndScrapAttachedViews method according to the situation will View recycled to the corresponding cache, then look at the specific process, since it is the first layout, RecyclerView no children in the View, so now the method no dice.

Next, at (2), the fill method is more important because it fills the layout. Take a look at the method:

LinearLayoutManager#fill

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {

	/ / during layout layoutState mScrollingOffset value is set to
	// layoutstate. SCROLLING_OFFSET_NaN does not enter this if block
        if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) {// ...
            recycleByLayoutState(recycler, layoutState);
        }
		
	// Space to fill
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
	// There is space to fill and the number of items is not enough
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
	    // ...
	
	    / / (1)
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

	    // Calculate the remaining space

	    // Layout does not go into the if block
            if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) {// ...
                recycleByLayoutState(recycler, layoutState);
            }
			
		// ...}}Copy the code

Look at the layoutChunk method at (1), which keeps calling as long as there is space left to fill:

LinearLayoutManager#layoutChunk

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
	/ / (1)
        View view = layoutState.next(recycler);

	// ...
		
	// By default, layoutstate. mScrapList is null
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
		/ / (2)
                addView(view);
            } else {
                addView(view, 0); }}else {
		// ...}}Copy the code

(2) addView method is not much to say, this method will get the child View to add RecyclerView. Look at (1) and see where the subview comes from:

    View next(RecyclerView.Recycler recycler) {
        // ...
        
        final View view = recycler.getViewForPosition(mCurrentPosition);

        return view;
    }
Copy the code

Does this sound familiar? Yes, it is the Recycler’s getViewForPosition method analyzed previously.

However, since there is no cache, the first layout is created through the createViewHolder of the Adapter, and no cache is added.

Update the list

The list can be updated using Adapter’s set of notify methods, two of which are examined here: notifyDataSetChanged and notifyItemChanged(int).

Adapter#notifyDataSetChanged

This method ends up calling the onChanged method of RecyclerViewDataObserver:

    @Override
    public void onChanged(a) {
	// ...

	// This method does two main things
	// add FLAG_UPDATE and FLAG_INVALID to all ViewHolder
	// 2. By default (mHasStableIds is false), clear CacheViews
        processDataSetCompletelyChanged(true);
		
        if(! mAdapterHelper.hasPendingUpdates()) {// Redraw the viewrequestLayout(); }}Copy the code

LinearLayoutManager (onLayoutChildren); LinearLayoutManager (onLayoutChildren); LinearLayoutManager (onLayoutChildren);

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
		// ...
		
		detachAndScrapAttachedViews(recycler);
		
		// ...
    }
Copy the code

Main difference is that a detachAndScrapAttachedViews method, it began to work this time, the method is defined in the LayoutManager RecyclerView, see its implementation:

LayoutManager#detachAndScrapAttachedViews

    public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
        final int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            finalView v = getChildAt(i); scrapOrRecycleView(recycler, i, v); }}Copy the code

RecyclerView scrapOrRecycleView () recycleView ();

    private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
	// Cannot recycle ViewHolder with FLAG_IGNORE added
	// This tag can be added to the corresponding View via ignoreView of LayoutManager
        if (viewHolder.shouldIgnore()) {
            return;
        }
	// if these conditions are met, enter the if block
        if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); }else {
            // ...}}Copy the code

Here to remove the child View and through the Recycler recycleViewHolderInternal method to recycle:

Recycler#recycleViewHolderInternal

        void recycleViewHolderInternal(ViewHolder holder) {
	    // ...
            boolean cached = false;
            boolean recycled = false;

            if (forceRecycle || holder.isRecyclable()) {
		// Since the ViewHolder is FLAG_INVALID, the if block is not entered
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
			/ /...
                }
		// cached is still false, enter the if block
                if(! cached) {// Cache the ViewHolder via putRecycledView of RecycledViewPool
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true; }}// ...
        }
Copy the code

The removed child View is cached in RecycledViewPool.

Later, when you call the fill method to fill the layout, you can retrieve the cached View from the RecycledViewPool.

Adapter#notifyItemChanged

This method passes in an int argument that represents the position with the updated item.

    public final void notifyItemChanged(int position) {
        mObservable.notifyItemRangeChanged(position, 1);
    }
Copy the code

The onItemRangeChanged method of RecyclerViewDataObserver is called:

    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // An UpdateOp is created in mAdapterHelper to save the information
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            // Execute this method if the update operation is availabletriggerUpdateProcessor(); }}Copy the code

Moving on to the triggerUpdateProcessor method:

    void triggerUpdateProcessor(a) {
        // The judgment condition defaults to false and the else block is executed
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            // ...
        } else {
            mAdapterUpdateDuringMeasure = true; requestLayout(); }}Copy the code

After saving some information, redraw the view. LinearLayoutManager: DispatchLayoutManager: dispatchLayoutStep1

RecyclerView#dispatchLayoutStep1

    private void dispatchLayoutStep1(a) {
        // ...
        
        processAdapterUpdatesAndSetAnimationFlags();
        
        // ...
    }
Copy the code

Basically see processAdapterUpdatesAndSetAnimationFlags method, can be seen from the name, it is responsible for updating adapter information:

    private void processAdapterUpdatesAndSetAnimationFlags(a) {
	// ...

        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }

	// ...
    }
Copy the code

Here we use mAdapterHelper, which eventually calls the viewRangeUpdate method of RecyclerView via the interface callback (which calls back the markViewHoldersUpdated method) :

    void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
        // ...

        for (int i = 0; i < childCount; i++) {
            // ...
            
            if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                / / (1)
                holder.addFlags(ViewHolder.FLAG_UPDATE);
                // ...}}}Copy the code

The way to do that is to go through all the child views, find all the child views that have changed, and do something about it. Focus here on comment (1), which adds the FLAG_UPDATE flag for the changed ViewHolder. Keep that in mind. We’ll use it later.

Then see onLayoutChildren method, as well as notifyDataSetChanged, main difference is is that detachAndScrapAttachedViews method, this method traverses the View, Call scrapOrRecycleView ();

LayoutManager#scrapOrRecycleView

    private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
	// ...
	
	// This time ViewHolder does not add FLAG_INVALID to the else block
        if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) {// ...
        } else{ detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

This is different from notifyDataSetChanged, because the FLAG_INVALID flag was not added to the ViewHolder before the view was redrawn, and this time the else block is entered.

First detach the View from RecyclerView (not remove it). The Recycler’s scrapView method is invoked to recycle the Recycler. This method has also been analyzed previously, so let’s look at it again:

    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
		
	If one of these conditions is met, the if loop is entered
	// 1. ViewHolder sets FLAG_REMOVED or FLAG_INVALID
	// 2. ViewHolder does not set FLAG_UPDATE
	// 3. No animation is set or the animation can reuse the ViewHolder
        if(holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {// ...
            
            mAttachedScrap.add(holder);
        } 
	// If none of the above conditions is met, cache the View into mChangedScrap
	else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this.true); mChangedScrap.add(holder); }}Copy the code

We can see from the previous analysis that for the ViewHolder that changed, FLAG_UPDATE was set, so it now fails to meet all three criteria and goes to the else block, and for the other ViewHolder, Since FLAG_UPDATE is not set, condition 2 is met and the if loop is entered.

So when updating the list with notifyItemChanged, the child views that have changed are cached in ChangedScrap and the child views that have not changed are cached in AttachedScrap. Later, when filling the layout, we can get sub-views from the appropriate Scrap cache for different items.

In addition, the Scrap cache only works in the layout phase, and the mAttachedScrap and mChangedScrap will be cleared in step3 of layout.

Another common scenario is sliding, where a child View that slides out of the screen will be cached in mCachedView, but I won’t go into detail here. I’ll cover sliding later in another article.

Afterword.

This paper focuses on the Recycler cache mechanism, what it is, when it is invoked, what it does, and what kind of cache can be used in different situations through its member variables.

Recycler is connected to LayoutManager layout and animation, for example, LayoutManager is responsible for layout, picking up and recycling views, and recycling is done by Recycler. These will be explained in more detail later when other aspects of RecyclerView are analyzed.

reference

  • RecyclerView source code analysis
  • RecyclerView source code analysis
  • Reuse mechanism of RecyclerView
  • RecyclerView cache analysis
  • RecyclerView source code analysis (three) – RecyclerView cache mechanism