The cache structure

Recycler cache ViewHolder has four levels of Recycler cache, from highest priority to lowest:

  • ArrayList mAttachedScrap
  • ArrayList mCachedViews
    • MAttachedScrap and mCachedViews are matched by the following criteria
      1. Whether the Item is removed
      2. Are the viewtypes of items the same
      3. Check whether itemids are the same
  • ViewCacheExtension mViewCacheExtension
  • RecycledViewPool mRecyclerPool
The cache Involves the object role Recreate the View View(onCreateViewHolder) Rebind data (onBindViewHolder)
Level 1 cache mAttachedScrap Cache the ViewHolder of the visible scope on the screen false false
The second level cache mCachedViews Cache sliding ViewHolder to be separated from RecyclerView, according to the position or ID of the child View cache, the default maximum storage of 2 false false
Three levels of cache mViewCacheExtension Developer-implemented caching
Four levels of cache mRecyclerPool The ViewHolder cache pool is essentially a SparseArray, where key is ViewType(int type) and value holds ArrayList< ViewHolder>. By default, each ArrayList can hold up to five Viewholders false true
  1. When RecyclerView slides, it triggers onTouchEvent#onMove, and the recycle and reuse ViewHolder will start there.
  2. LayoutManager is responsible for RecyclerView layout, including the acquisition and reuse of ItemView.
  3. Will call to tryGetViewHolderForPositionByDeadline () method returns the ViewHolder.

  • ViewHolder obtained with mAttachedScrap, mCachedViews, and mViewCacheExtension does not require the layout and binding data to be recreated.
  • ViewHolder retrieved from the cache pool mRecyclerPool does not need to recreate the layout, but does need to rebind the data.
  • If the target ViewHolder is not retrieved from either of the caches, then Adapter#onCreateViewHolder is called to create the layout and Adapter#onBindViewHolder is called to bind the data.

ViewCacheExtension

  • Use when the ViewHolder is fixed in position, fixed in content, and limited in number
//1. When the viewType is TYPE_SPECIAL, set the level 4 cache pool RecyclerPool to not store the corresponding type of data, because developers need to cache by themselves
recyclerView.getRecycledViewPool().setMaxRecycledViews(DemoAdapter.TYPE_SPECIAL, 0);
//2. Set the ViewCacheExtension cache
recyclerView.setViewCacheExtension(new MyViewCacheExtension());

//3. Implement custom cache ViewCacheExtension
class MyViewCacheExtension extends RecyclerView.ViewCacheExtension {
    @Nullable
    @Override
    public View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position, int viewType) {
        // If viewType is TYPE_SPECIAL, use your own cached View to build ViewHolder
        // If null is returned, RecyclerPool cache will be used or onCreateViewHolder will be used to construct the View and ViewHolder
        return viewType == DemoAdapter.TYPE_SPECIAL ? adapter.caches.get(position) : null; }}@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
   if (holder instanceof SpecialHolder) {
      SpecialHolder sHolder = (SpecialHolder) holder;
      sHolder.tv_ad.setText(mDatas.get(position));
      //4. This is the key point, according to position to put the View into the custom cache
      caches.put(position, sHolder.itemView);
  } else if (holder instanceofCommonHolder) { .... }}@Override
public int getItemViewType(int position) {
   if (position == 0) {
       return TYPE_SPECIAL;//5. The first position View and data are fixed
   } else {
       returnTYPE_COMMON; }}Copy the code
  • After setting the ViewCacheExtension cache, through getViewForPositionAndType ViewHolder is created for the first time after correction according to the position and to gain the cache ViewCache type

How to recycle Item

Limit contact line

RecyclerView will calculate the specific filling item according to the scrolling displacement value, and recycle item through the Limit invisible line

  • The initial value of the Iimit invisible line is the distance between the current tail Item of RecyclerView and the bottom of the list, that is, the maximum sliding distance without filling a new Item
  • The pixel value consumed by each newly filled Item is added to the Limit invisible line, which moves down with the fill two of the Item
  • Items above the Limit invisible line are collected
  • Each new Item is checked to see if any of the loaded items are recyclable
The Item of animation
  • Except when the Item animation is finished, events are passed to a listener inside RecyclerView, which triggers the recycling of the Item
Remove the Item

If the Item is removed from the screen, it will be recycled to mCachedViews. If it happens to be deleted, it will be deleted from mCachedViews and added to mRecyclerPool

pre-layout & post-layout

  • In order to realize the Item animation, two layouts, pre-Layout and post-Layout, will be passed at the beginning and end of the animation respectively to obtain the corresponding snapshot to realize the animation.
  • RecyclerViewIn order to achieve Item animation, two layouts, the first pre-layout, the second real layout, in the source code asLayoutManager.onLayoutChildren()Called twice
  • mState.mInPreLayoutThe value marks the lifetime of the pre-layout. The pre-layout process beginsRecyclerView.dispatchLayoutStep1()Finally,RecyclerView.dispatchLayoutStep2(). Two callsLayoutManager.onLayoutChildren()Different branches of logic are performed depending on the tag bit.
  • Implementation through dispatchLayoutStep1 () method, call to LayoutManager. OnLayoutChildren () method, this method is used to layout all the Item in the Adapter. If the Item animation is supported, onLayoutChildren() is called twice, the first time called pre-layout, which is a pre-layout before the actual layout of the Item.

LayoutManager.onLayoutChildren()

  • In order to make the two layouts do not affect each other, it is necessary to clear the contents of the last layout before each layout, but the two layouts basically have little change, so Recyclerview adopts time to change space, and cache Item into Scrap before cleaning. The corresponding ViewHolder can be retrieved from the cache during the actual population.
  • During the pre-layout phase, if a deleted Item is encountered during the loop filling, the space occupied by that Item is ignored because it will not be loaded onto the screen.
  • Detach View and Remove View are similar in that they remove the child control from the parent control’s child list. The only difference is that Detach is lighter and does not trigger redrawing. Detach is also ephemeral and cached into the mAttachedScrap list. Immediately after the populate stage, remove the detach entries from the mAttachedScrap and attach them again

Animation processing

  • RecyclerView uses an Int value to mark different states

    public class RecyclerView {
        public static class State {
            static final int STEP_START = 1;// The beginning of the layout
            static final int STEP_LAYOUT = 1 << 1;// The layout stage of the layout
            static final int STEP_ANIMATIONS = 1 << 2; // The layout animation stage
            int mLayoutStep = STEP_START; // Current layout stage}}Copy the code
    1. The state is marked as STEP_ANIMATIONS when dispatchLayoutStep2() is over, so the layout animation phase is in progress, and dispatchLayoutStep3() is the animation process
  • Through ItemAnimator. RecordPostLayoutInformation () one by one to build the Item ItemHolderInfo animation information, ItemHolderInfo format is as follows

    	   // Item 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 position of Item
                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
  • ItemHolderInfo contains information about the relative position of the Item, which is then saved to a ViewInfoStore, which is used to store animation information about the Item

    class ViewInfoStore {
        // Use ArrayMap to store ViewHolder and its corresponding animation information
        final ArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
        // Store the post-Layout Item 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
        }
        
        // Store the pre-Layout Item 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
        }
        
        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; // Item displays a flag
            RecyclerView.ItemAnimator.ItemHolderInfo preInfo;// Pre-layout Item position information
            RecyclerView.ItemAnimator.ItemHolderInfo postInfo;// Post-layout Item position information
            // 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
    1. InfoRecord uses oneintThe flag bit of a typeWhat layout stages did Item go through. If the Item animation information is added during the post-Layout phase, its flag bit is appendedFLAG_POSTThis bit is used to determine what kind of animation to do. Finally, the entry animation information is bound to the corresponding ViewHolder and stored in the ArrayMap structure.
    2. AddToPreLayout () and addToPostLayout() call their methods on pre-layout and post-Layout respectively to bind ViewHolder and InfoRecord
  • RecyclerView experienced pre-layout and post-layout and animation layout stage, ViewInfoStore saved every Item involved in animation, internal contains pre-layout location information + post-layout location information + experienced layout stage, and finally call process() to execute animation.

  • The animation execution phase will iterate over all saved items in ViewInfoStore and execute specific animation based on flag bits. The animation callback type is as follows:

    interface ProcessCallback {
        	// Disappear animation, before the layout, will not be added to the layout
            void processDisappeared(RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo preInfo,
                    @Nullable RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
        	// Animation appears in the layout, but does not re-fit
            void processAppeared(RecyclerView.ViewHolder viewHolder, @Nullable RecyclerView.ItemAnimator.ItemHolderInfo preInfo,
                    RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
        	// Keep the animation, keep both animations still
            void processPersistent(RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo preInfo,
                    @NonNull RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
        	// Disappear immediately after appearing, invalid for animation, normal callback
            void unused(RecyclerView.ViewHolder holder);
        }
    Copy the code
  • Once animation instructions and data are received, they are encapsulated as MoveInfo, and different types of animations are stored in different MoveInfo lists. The logic for executing animations is then thrown into Choreographer’s animation queues, and when the next vSYNC signal arrives, Choreographer takes out the animation from the animation queues and executes the animation of the table items, executing the animation by walking through all MoveInfo lists, Build a ViewPropertyAnimator instance for each MoveInfo and start the animation.