Recycle view source code walk through

recycleview has been widely used in previous projects. Now, you can check the source code in your spare time. The record is as follows.

Overview of recycleview

recycleview is widely used in projects that need to display lists. The common usage is as follows:
1. Define adapter
2. Add recycleview to the layout file
3. Initialize recycleview in activity or fragment, and set adapter and layoutmanager for it.
4. notifyDataSetChanged method for calling adapter after getting data.
After that, the list is displayed on the screen, which is just the simplest and most basic use of recycleview. All subsequent analysis processes are based on the simplest and most basic use functions.

Overview of related categories:
  1. Adapter: the abstract internal class in recycleview is responsible for the view generation of list items, binding items with data, sending a message to inform you to add or delete items or modify the content of existing items.
  2. LayoutManager: complete the layout management of item items, such as horizontal and vertical
  3. Recycler: mainly responsible for item recycling
  4. ViewHolder: the carrier of list item
Analysis process

This paper focuses on the following major processes for analysis:

  1. Initialization, measurement and layout of recycleview
  2. setLayoutManager and setAdapter processes of recycleview
  3. Recycling mechanism logic of recycleview
Initialization, measurement and layout of recycleview

The initialization method is as follows:

    public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        ......

        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);

        mItemAnimator.setListener(mItemAnimatorListener);
        initAdapterManager();
        initChildrenHelper();
        
    }

In the initialization method, the emphasis is to initialize the AdapterHelper and ChildHelper, which are two auxiliary classes to assist in sorting and carrying out the adapter update operation, and to assist in the management of children's functions in recycleview. This article will not focus on this. Next, we will analyze the onMeasue process in detail, assuming that the adapter and layoutmanager have been set, and layoutmanager is linearlayoutm Manager, the process is as follows:

The main code flow is analyzed according to the running time sequence as follows:
The analysis here is after setting the layoutmanager to linearlayoutmanager

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        //Automeasure is set to true in LinearLayoutManager initialization method
        if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                    && heightMode == MeasureSpec.EXACTLY;
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            //If the width and height of recycleview are exact values or the adapter has not been set, skip the measurement
            if (skipMeasure || mAdapter == null) {
                return;
            }
            //dispatchLayoutStep1 can be seen from the comments that its function does not involve specific item acquisition measurement and so on
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

           ......
        }
    }

From the code annotation, we can see that the function of dispatchLayoutStep1 is:

  1. Processing adapter updates
  2. Decide which animation to play
  3. Save some information about the current view
  4. Pre layout and save information if necessary
    Instead of focusing on the key points here, focus on dispatchLayoutStep2. The actual layout operation is completed in this method
private void dispatchLayoutStep2() {
        ......
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        //Layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        ......
    }

onLayoutChildren is implemented in the subclass of LayoutManager. Here is LinearLayoutManager. According to the comments, the main layout logic is as follows:

  1. Through the check sub view and other variables, the anchor coordinates and positions are obtained
  2. Fill from bottom stack to start
  3. Fill from top stack to end
  4. Scroll to meet the requirements, like stacking from the bottom
    There are many code functions. Here are only a few main analysis methods
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    Omit the process of removing and recycling all items in case of conflict between the number of items recorded in state and the state of mPendingSavedState
    Initialization process of mLayoutState and mrientationhelper
    Deal with the layout direction problem. When the layout of mborientation is vertical and not rtl, it should be from left to right. Otherwise, it should be from right to left
    Calculate anchor coordinates and positions in updateAnchorInfoForLayout
    Omit pre layout related operations
    
    
    ......
    Here is the work related to item recycling    
    detachAndScrapAttachedViews(recycler);
    
    ......
    The main acquisition of itemview and filling measurement are implemented here
    fill(recycler, mLayoutState, state, false);
    ......
    }
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
    final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            //When the rolling offset is not equal to the minimum value, recycle once
            recycleByLayoutState(recycler, layoutState);
        }
        
    ......
    
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state))
    {
            layoutChunkResult.resetInternal();
            //Get the view and add it to the parent layout, then measure it
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
        
            ......
            //Detect the rolling offset again and carry out a recycling operation
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
        
    }
}

In the fill method, the layout is filled according to the layoutState, and the rolling offset is recycled twice. According to the offset of the layoutState and the data in the adapter, the view is acquired continuously, added to the current layout, and the onmeasure operation is carried out. First, the view acquisition method layoutChunk is analyzed

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //Get the view, which involves the item recycling and reuse logic    
        View view = layoutState.next(recycler);
        ......
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        //The width and height measurement of item is realized here
        measureChildWithMargins(view, 0, 0);
        ......
    }

layoutChunk obtains the view through layoutState. This process involves the recycling and reuse mechanism of recycleview. After getting it, it determines whether to add it to the current recycleview by using addView or adddisappearaingview through the non null of mScrapList. Here, adddisappearaingview indicates that it is added to recyclview and will no longer be displayed. After the completion of the completion, the measureChildWithMargins method called the parent class is actually measured.

        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;

            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                    getPaddingLeft() + getPaddingRight() +
                            lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                    getPaddingTop() + getPaddingBottom() +
                            lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                    canScrollVertically());
            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }
        }

At this point, the onMeasure process analysis of recycleview is over. It involves the collection and reuse of items, and the actual measurement of items. Then, it makes a detailed analysis. First, let's look at the onLayout code process.

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }
    void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
                mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

It can be seen that the processes after calling dispatchLayoutStep1, dispatchLayoutStep2, and dispatchLayoutStep3 in turn are consistent with the onMeasure processes analyzed before when the mdadapter and mLayout are not empty. We will not make a detailed analysis here.
The ondraw code is as follows:

    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

In addition to calling the parent class to draw, we also draw the split line. The related content of the split line will not be analyzed here. The initialization, measurement and layout of recycleview are finished.

setLayoutManager and setAdapter processes of recycleview

setLayoutManager mainly sets a layout management implementation for recycleview. Before setting, it will clear the mRecycler once. After setting, it will request to update the cache size to update the layout. It will go through the onMeasure and onLayout processes again

    public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) {
            return;
        }
        stopScroll();
        //Remove existing view and empty recycle bin
        if (mLayout != null) {
            ......
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
            mRecycler.clear();

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();
        }
        
        mChildHelper.removeAllViewsUnfiltered();
        //Assign layout
        mLayout = layout;
        if (layout != null) {
            if (layout.mRecyclerView != null) {
               ......
            }
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        //Update cache size
        mRecycler.updateViewCacheSize();
        //Request to update layout
        requestLayout();
    }

The setAdapter process is similar to the setLayoutManager process. After setting the adapter, the layout will be updated again, and onMeasure and onLayout will be rerouted,

    public void setAdapter(Adapter adapter) {
        //Freeze layout, stop scrolling and layout
        setLayoutFrozen(false);
        //Set up a new adapter
        setAdapterInternal(adapter, false, true);
        //Request to update layout
        requestLayout();
    }
    public void setLayoutFrozen(boolean frozen) {
        //mLayoutFrozen initial value is false, here the conditional statement cannot enter
        if (frozen != mLayoutFrozen) {
            assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
            if (!frozen) {
                //assignment
                mLayoutFrozen = false;
                if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
                    requestLayout();
                }
                mLayoutRequestEaten = false;
            } else {
                //This is where the setAdapter will be executed after the first call
                final long now = SystemClock.uptimeMillis();
                MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                //Send a cancellation event
                onTouchEvent(cancelEvent);
                //mLayoutFrozen is assigned only on setLayoutFrozen
                mLayoutFrozen = true;
                mIgnoreMotionEventTillDown = true;
                stopScroll();
            }
        }
    }
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        //If the previous mdadapter is not empty, you need to remove listening first
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        //The passed compatibleWithPrevious is false, and removeAndRecycleViews is true. You can enter this conditional statement
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            ......Omit animation related
            if (mLayout != null) {
                mLayout.removeAndRecycleAllViews(mRecycler);
                mLayout.removeAndRecycleScrapInt(mRecycler);
            }
            //Clear the mRecycler before updating the adapter to ensure the correct callback
            mRecycler.clear();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            //Registered observer
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        //Send adapter change notification to mLayout and mRecycler
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        //Mark existing view s as obsolete
        markKnownViewsInvalid();
    }

First, the observer mode of adapter in recycleview is introduced. The data view is updated by calling the notifyDataSetChanged method of adapter. The relationships of several related classes are as follows:

This is the observer pattern

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            //Notify all observers in turn
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
}
    void markKnownViewsInvalid() {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            }
        }
        //Set msinsetdirty to true in layoutparam for all elements in mCachedViews
        markItemDecorInsetsDirty();
        mRecycler.markKnownViewsInvalid();
    }
        void markKnownViewsInvalid() {
            if (mAdapter != null && mAdapter.hasStableIds()) {
            //Set flag "invalid for all views in mCachedViews
                final int cachedCount = mCachedViews.size();
                for (int i = 0; i < cachedCount; i++) {
                    final ViewHolder holder = mCachedViews.get(i);
                    if (holder != null) {
                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
                        holder.addChangePayload(null);
                    }
                }
            } else {
                // Add all views to the mRecyclerPool and clear the mCachedViews
                recycleAndClearCachedViews();
            }
        }
Recycling mechanism logic of recycleview

Recycling logic here can be simply divided into two cases of recycling according to the code. One is to recycle all items displayed on the screen during layout, and the other is to recycle items marked out of the screen during scrolling.

Recycle during layout

Every time the layout changes, onmeasure, onlayout, setlayoutmanager, setadapter, etc. will call the onLayoutChildren method of layoutmanager, which will unconditionally call the detachAndScrapAttachedViews method, and detachAndScrapAttachedViews will set all current views to be discarded.
The following figure is the recovery sequence diagram during layout:
[failed to save the pictures in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly

        public void detachAndScrapAttachedViews(Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 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);
            if (viewHolder.shouldIgnore()) {
                if (DEBUG) {
                    Log.d(TAG, "ignoring view " + viewHolder);
                }
                return;
            }
            //If the viewholder data is valid and has not been removed, and the adpater does not have a fixed id
            if (viewHolder.isInvalid() && !viewHolder.isRemoved() &&
                    !mRecyclerView.mAdapter.hasStableIds()) {
                //Remove the viewholder from the child helper and return to recycleview
                removeViewAt(index);
                //The viewholder will be added to the mCachedViews list or RecycledViewPool
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                //Callback to detachViewFromParent in recycleview
                detachViewAt(index);
                //Add view to mattachedscratch or mchangedsrap
                recycler.scrapView(view);
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }
        }

The hasStableIds method of the adapter returns false by default. When updating the adapter or returning the viewholder, set the flag "invalid". Here we focus on two methods: recycleViewHolderInternal and scrapView.

        void recycleViewHolderInternal(ViewHolder holder) {
            
            ......Ellipsis pairs isScrap,isTmpDetached,shouldIgnore Method returns true When an exception code is thrown, it is filtered to be marked as obsolete or temporarily detach Of item´╝îPrevent recycling.
            //false is returned here without special settings
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            //If it is not specially set here, false will also be returned
            final boolean forceRecycle = mAdapter != null
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;
            
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                                | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
                    //If the size of mCachedViews has exceeded the maximum, add the first few item s to the recycleviewpool until the size is appropriate
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    
                    ......Omit prefetch correlation
                    //Add to mCachedViews
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    //Add to RecycledViewPool if not added to mCachedViews
                    addViewHolderToRecycledViewPool(holder);
                    recycled = true;
                }
            } else if (DEBUG) {
                Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                        + "re-visit here. We are still removing it from animation lists");
            }
            
        }
        void addViewHolderToRecycledViewPool(ViewHolder holder) {
            ViewCompat.setAccessibilityDelegate(holder.itemView, null);
            //This will call back to the onViewRecycled method of the adapter
            dispatchViewRecycled(holder);
            holder.mOwnerRecyclerView = null;
            getRecycledViewPool().putRecycledView(holder);
        }
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList scrapHeap = getScrapHeapForType(viewType);
            //If the existing size has exceeded the set maximum, return
            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            //Add to list
            scrapHeap.add(scrap);
        }

There are two main lists in RecycledViewPool, mScrap: save different viewholder lists according to the viewholder type, mmaxsrap: record the maximum value of different types of viewholder lists.

        private SparseArray<ArrayList<ViewHolder>> mScrap =
                new SparseArray<ArrayList<ViewHolder>>();
        private SparseIntArray mMaxScrap = new SparseIntArray();

Return to the graporrecycleview method. If the conditions are not met, the following process will be entered

        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            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.");
                }
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

At this point, the item recycling process from detachenscrapatachedviews is over. To sum up, all views will be added to the mCachedViews list in recycler first. If the size of mCachedViews has reached the maximum, the previous one will be added to the RecycledViewPool. In the RecycledViewPool, different types of viewholders will be used for storage, and different views will be stored The viewholder list of type has a maximum limit, or it will be added to the maattachedsnap or mchangedsnap list

Recycle on roll

Enter the recycling process in LinearLayoutManager, and there is another entry: recycleByLayoutState method, which is in the condition judgment statement every time it is called:

if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
          
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }

Here, mscollingoffset records not the sliding offset, but how much more can we scroll without adding a new item. Only when the sliding offset is not equal to the default minimum value, can we enter the recycling process. The only process for mscollingoffset to be assigned not equal to the value of scrolling \ offset \ Nan is as follows:

The following figure is the recovery sequence diagram after entering recycleByLayoutState. Here, only one direction is selected for analysis, and the logic of the other direction is the same as this:

    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }

Recycleviews fromend from the perspective of annotation translation: recycle views beyond the layout when sliding towards the beginning of the layout

    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
        final int childCount = getChildCount();
        //If the mcscrollingeoffset passed in is a value less than 0, it will return
        if (dt < 0) {
            return;
        }
        //dt here is the largest but the height of an item, so the limit is very large
        final int limit = mOrientationHelper.getEnd() - dt;
        if (mShouldReverseLayout) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                //Here, we recycle the view whose starting position is greater than the limit. As we have known before, the limit is very large,
                //So the item that can be bigger than him must be the item near the bottom
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        } ......Similar process is omitted here, not in reverse layout
    }

Here is how to calculate whether an item has exceeded the recycleview height range. The incoming mscollingoffset is the maximum rolling distance of a record without generating a new item, so the distance will not exceed the height of an item. The OrientationHelper is an abstraction class. The specific implementation is divided into two cases: vertical and horizon. Here, the msorientationhelper.getend( )It returns the height of recycleview, minus a maximum item height value. The limit is still very large. The range is just between the recycleview height and an item height. Therefore, if the top value of an item can be greater than the limit, it must be an item that has exceeded the range.

    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
        }
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }
        public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index);
            removeViewAt(index);
            recycler.recycleView(view);
        }

For the moment, only the Recycler analysis recycleView method is used:

        public void recycleView(View view) {
            // This public recycle method tries to make view recycle-able since layout manager
            // intended to recycle this view (e.g. even if it is in scrap or change cache)
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()){
                holder.clearReturnedFromScrapFlag();
            }
            recycleViewHolderInternal(holder);
        }

Finally, I entered the recycleViewHolderInternal method, which has been analyzed in detail before.

Multiplexing logic

In the update layout, the reuse logic of recycleview is used to obtain itemview. The acquisition process of viewholder is described as follows:

  1. First try to get the holder from the mchangedsrap list (if the pre layout conditions are met, not setting will not be met)
  2. Try to get the holder from the mmattachedsrap list through position, and continue to get the holder from the mCachedViews through position if not
  3. If there is a stable ids, try to get it from the list of mmattachedsnap and the list of mCachedViews through id
  4. If mViewCacheExtension is not empty, get view through mViewCacheExtension, and then get viewholder
  5. Get holder from RecycledViewPool
  6. Create a new viewholder through the createViewHolder of adapter
    Each of the above steps is continued on the basis that the holder was not successfully obtained in the previous step. If one step is obtained, it will directly enter the next stage, data binding. Here, it will be judged whether the pre layout or data has been bound. If not, it will call the bindViewHolder of adapter to bind data to the holder. At this point, the acquisition of holder and data binding are completed. The following is the flow chart:

        View getViewForPosition(int position, boolean dryRun) {
            ......
            boolean fromScrap = false;
            ViewHolder holder = null;
            // 0) if isplayout is set, try to get it from the mchangedsrap list
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Get from matachedsrap and mCachedViews through position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                ......
                }
            }
            if (holder == null) {
               ......
                // 2) If there is a stable id, try to get it from mtattachedsnap and mCachedViews through ID
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                //If mViewCacheExtension is not empty, an attempt is made to get a holder through it
                if (holder == null && mViewCacheExtension != null) {
                  
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        ......
                    }
                }
                if (holder == null) { // fallback to recycler
                    .....
                    //Try to get from RecycledViewPool
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                //Create a new viewholder by calling createViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }

            ......Omit animation related
            
            boolean bound = false;
            //If it is not a pre layout, and no data is bound, and the viewholder needs to be updated,
            //Call the bindViewHolder of adapter to bind the data and update the viewholder view
            if (mState.isPreLayout() && holder.isBound()) {
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                ......
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                holder.mOwnerRecyclerView = RecyclerView.this;
                mAdapter.bindViewHolder(holder, offsetPosition);
                ......
            }

            ......
            return holder.itemView;
        }

Here is the layout principle of recycleview, and the process of recycling principle is basically clear,

Published 57 original articles, won praise 2, visited 6346
Private letter follow

Tags: Fragment less

Posted on Mon, 10 Feb 2020 07:05:00 -0500 by Simsonite