RecyclerView since its release, has been favored by developers, its good function decoupling, so that we become comfortable in the customization of its functions. Since using this control in my project, I’ve been so excited about it that I want to analyze it in a series of articles. This article starts with the most basic display analysis, for the analysis of the back to lay a solid foundation.

The basic use

This article first analyzes the RecyclerView from the creation to the display process, we first look at its basic use

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(new RvAdapter());
Copy the code

LayoutManager and Adapter are indispensable parts of RecyclerView. This article will analyze this code.

For convenience, in the later analysis, I’ll use RV for RecyclerView, LM for LayoutManager, and LLM for LinearLayoutManager.

The constructor

The View constructor is usually used to parse properties and initialize variables, and the RV constructor is no exception, and the relevant code for this article is as follows

    public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        // ...

        mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
            // ...
        });
        
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            // ...
        });

        // ...
    }
Copy the code

You can roughly guess what these two classes do from their full names. AdapterHelper is an Adapter helper class that handles Adapter updates. ChildHelper is an RV helper class that manages its child views.

Sets the LayoutManager

    public void setLayoutManager(@Nullable LayoutManager layout) {
        // ...
        
        / / save the LM
        mLayout = layout;
        if(layout ! =null) {
            LM saves the RV reference
            mLayout.setRecyclerView(this);
            // If the RV is added to the Window, notify LM
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this); }}// ...
        
        // Request a relayout
        requestLayout();
    }
Copy the code

The main action of the setLayoutManager() method is that the RV and LM save references to each other, and since the RV’s LM has changed, the layout needs to be rerequested.

Set the Adapter

The setAdapter() method is implemented by setAdapterInternal()

    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        // ...
        
        // RV Saves Adapter references
        mAdapter = adapter;
        if(adapter ! =null) {
            // Register the listener for the new Adapter
            adapter.registerAdapterDataObserver(mObserver);
            // Notify the new Adapter has been added to the RV
            adapter.onAttachedToRecyclerView(this);
        }
        // If LM exists, notify LM that Adapter has changed
        if(mLayout ! =null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        // Notify the RV that Adapter has changed
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        // Indicates that the Adapter has changed
        mState.mStructureChanged = true;
    }
Copy the code

The RV saves the Adapter reference and registers listeners for the new Adapter, and then notifies each listener concerned about the Adapter, such as RV, LM.

measurement

When everything is ready, it’s time to analyze the measurement

    protected void onMeasure(int widthSpec, int heightSpec) {
        // ...
        // If LM uses automatic measurement mechanism
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            // For compatibility processing, the RV's defaultOnMeasure() method is actually called
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            // If both width and height are measured in EXACTLY mode, then use the default measurement and return it directly
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            / /... Omit the rest of the measurement code
        } else {
            // ...}}Copy the code

First, the measurement logic is determined based on whether LM supports the RV’s automatic measurement mechanism. LLM supports automatic measurement mechanism, so only the measurement in this case is analyzed.

What is the automatic measurement mechanism, you can carefully read the source code comments and measurement logic, I only do a simple analysis here.

With automatic measurement, the LM onMeasure() is first called to take the measurement. Now, you might wonder, if it’s called automatic measurement, why do we measure it with LM. For compatibility purposes, it actually calls the RV’s defaultOnMeasure() method

    void defaultOnMeasure(int widthSpec, int heightSpec) {
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }
Copy the code

We can see that RV, as a ViewGroup, saves the measurement results without considering the subview. Obviously, this is a rough measure.

But this rough measurement is actually designed to satisfy a special case where the parent View gives the MeasureSpec.EXACTLY. As you can see from the code, this particular case is handled after this step of rough measurement.

To simplify the analysis, only this special (and most common) case is considered for now. The omitted code is actually the code that considers the subview measurement, and this code is also found in onLayout(), so I’ll cover it later.

layout

OnLayout is implemented by dispatchLayout()

    void dispatchLayout(a) {
        // ...
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            RV has been measured, so LM saves the RV measurement results
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if(mAdapterHelper.hasUpdates() || mLayout.getWidth() ! = getWidth() || mLayout.getHeight() ! = getHeight()) {// ...
        } else {
            // ...
        }
        dispatchLayoutStep3();
    }
Copy the code

The layout process, however, is done with dispatchLayoutStep1(), dispatchLayoutStep2(), and dispatchLayoutStep3(). The only thing relevant to this article is dispatchLayoutStep2(), which is the actual layout operation that completes the child View, implemented by LM’s onLayoutChildren().

LM implements the layout of child views

From the previous analysis, we can see that the RV’s layout of the subviews is left to LM. LLM is used in the example, so its onLayoutChildren() method is analyzed here. Since this method is quite a lot of code, it will be parsed in steps.

Initialization information

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // Ensure that mLayoutState is created
        ensureLayoutState();
        mLayoutState.mRecycle = false;
        // Resolve whether to use reverse layout
        if(mOrientation == VERTICAL || ! isLayoutRTL()) { mShouldReverseLayout = mReverseLayout; }else {
            mShouldReverseLayout = !mReverseLayout;
        }
    }
Copy the code

First, ensure that the LayoutState mLayoutState is created to hold the state of the layout.

In this example, LLM uses a vertical layout, and the layout uses a default that does not support RTL, so mShouldReverseLayout should be false, indicating that the layout is not inverted.

You need to know the reverse layout of the LLM.

Update anchor point information

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. Initialize information
        // 2. Update anchor information
        if(! mAnchorInfo.mValid || mPendingScrollPosition ! = RecyclerView.NO_POSITION || mPendingSavedState ! =null) {
            mAnchorInfo.reset();
            // Anchor information is stored in reverse layout
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // Calculate the position and coordinates of the anchor points
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            // Indicates that the anchor information is valid
            mAnchorInfo.mValid = true;
        }   
        
        // ...
        
        final int firstLayoutDirection;
        if (mAnchorInfo.mLayoutFromEnd) {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                    : LayoutState.ITEM_DIRECTION_HEAD;
        } else {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
        }
        // Notify the anchor information is ready
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    }
Copy the code

The AnchorInfo mAnchorInfo is used to hold the anchor information. The anchor location and coordinates indicate where the layout starts, as you’ll see later in the analysis.

MStackFromEnd (Boolean) is supported by the AbsListView#setStackFromBottom(Boolean) feature. In other words, it provides a consistent method of operation for developers. Personally, I think this is really a garbage operation.

After using updateAnchorInfoForLayout () method to calculate the position of the anchor and coordinates

    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo) {
        // ...
        // Determine the anchor coordinates based on the padding value
        anchorInfo.assignCoordinateFromPadding();
        // If not inverted, the anchor position is 0
        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    }
    
    // AnchorInfo#assignCoordinateFromPadding()
    void assignCoordinateFromPadding(a) {
        // If the layout is not reversed, the coordinate is the RV paddingTop value
        mCoordinate = mLayoutFromEnd
                ? mOrientationHelper.getEndAfterPadding()
                : mOrientationHelper.getStartAfterPadding();
    }    
Copy the code

Anchor#mPosition indicates the location where the data needs to be retrieved from the Adapter. Anchor#mCoordinate represents which coordinate point the subview needs to start filling the subview.

In the case of the example, the anchor point coordinate is the paddingTop of the RV, and the position is 0.

Calculate the extra space for the layout

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. Initialize information
        // 2. Update anchor information
        // 3. Calculate the extra space for the layout
        // Save the layout direction. In the case of no scrolling, the value is layoutstate.layout_end
        mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
                ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        // Calculate the extra space required for the layout and save the result to mReusableIntPair
        calculateExtraLayoutSpace(state, mReusableIntPair);
        // The padding should be considered
        int extraForStart = Math.max(0, mReusableIntPair[0])
                + mOrientationHelper.getStartAfterPadding();
        int extraForEnd = Math.max(0, mReusableIntPair[1])
                + mOrientationHelper.getEndPadding();
        if(state.isPreLayout() && mPendingScrollPosition ! = RecyclerView.NO_POSITION && mPendingScrollPositionOffset ! = INVALID_OFFSET) {// ...}}Copy the code

At the time of the RV sliding, calculateExtraLayoutSpace () allocates a page of extra space, other cases will not allocate extra space.

For example, in the case of calculateExtraLayoutSpace () allocate extra space is 0. But for the layout, the extra space also takes into account the RV padding.

If a custom inherited from LLM LM, can copy calculateExtraLayoutSpace () defines the extra space allocation policy.

Layout for child views

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. Initialize information
        // 2. Update anchor information
        // 3. Calculate the extra space for the layout
        // 4. Layout for child View
        // First detach and recycle the subview
        detachAndScrapAttachedViews(recycler);
        // The RV height is 0, and the mode is UNSPECIFIED
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mIsPreLayout = state.isPreLayout();
        mLayoutState.mNoRecycleSpace = 0;
        if (mAnchorInfo.mLayoutFromEnd) {
            // ...
        } else {
            // Fill backward from the anchor position
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtraFillSpace = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // Fill forward from the anchor position
            // ...

            // If there is extra space, fill more subviews backwards
            if (mLayoutState.mAvailable > 0) {
                // ...}}Copy the code

Before laying out the child View, first separate the child View from the RV and reclaim it. Then, fill the subview backwards and forwards from the anchor position by filling (), and finally try to continue filling the subview backwards (if any) if there is room left.

The anchor position calculated from the example is 0 and the coordinate is paddongTop, so only the backfilling process from the anchor position is analyzed here.

First call updateLayoutStateToFillEnd () method, according to the anchor point information to update the mLayoutState

    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
        // The position and coordinates of the anchor are passed in as arguments
        updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
    }

    private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
        // Free space is the size available after the padding is removed
        mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
        // Indicates the direction of data traversal for the Adapter
        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
                LayoutState.ITEM_DIRECTION_TAIL;
        // Save the location of the data to be obtained from the Adapter
        mLayoutState.mCurrentPosition = itemPosition;
        // Save the orientation of the layout
        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
        // Save the anchor coordinates, which are the offset of the layout
        mLayoutState.mOffset = offset;
        // Roll offset
        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
    }
Copy the code

Once the mLayoutState information has been updated, fill() is called to fill the subview

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        final int start = layoutState.mAvailable;
        // ...
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        // There is free space, and there are subviews that are not filled
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            // All child views have been displayed
            if (layoutChunkResult.mFinished) {
                break;
            }
            // Update the layout offset
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
    
            // Recalculate the available space
            if(! layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList ! =null| |! state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; remainingSpace -= layoutChunkResult.mConsumed; }// ...
        }
        // Return how much space is used in this layout
        return start - layoutState.mAvailable;
    }
Copy the code

As an example, if there is still free space and there are subviews that are not filled, then the layoutChunk() method will be called to fill the subview until the available space is exhausted or there are no subviews left.

LLM# layoutChunk () analysis

For the son View

LayoutChunk () is the core method that LLM uses to layout subviews. We need to focus on the implementation of this method. Since this method is also longer, I’m going to do it in sections as well

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        / / 1. To obtain a child View with the update mLayoutState. MCurrentPosition
        View view = layoutState.next(recycler);    
    }
Copy the code

RecyclerView. RecyclerView. RecyclerView

        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            
            // ...
            
            if (holder == null) {
                if (holder == null) {
                    / / 1. Callback Adapter. OnCreateViewHoler create ViewHolder (), and set the ViewHolder type
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    // ...}}// ...
            
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // ...
            } else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // 2. Call adapter.bindViewholder () to bind the ViewHolder and update some information about the ViewHolder
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            
            // 3. Make sure the layout parameters of the created View are correct and update the information
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if(! checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); }else {
                rvLayoutParams = (LayoutParams) lp;
            }
            // The layout parameter saves the ViewHolder
            rvLayoutParams.mViewHolder = holder;
            // If the View is not new and has already been bound, then mPendingInvalidate is true
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }
Copy the code

First, create a ViewHolder object by calling the ViewHolder#createViewHolder() method

    public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
        final VH holder = onCreateViewHolder(parent, viewType);
        holder.mItemViewType = viewType;
        return holder;
    }
Copy the code

Create a ViewHolder object with ViewHolder#onCreateViewHolder() and set the value mItemViewType to the ViewHolder object.

After the second step, create ViewHolder object, through ViewHolder# tryBindViewHolderByDeadline binding ViewHolder object () method

        private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
                int position, long deadlineNs) {
            // Set the ViewHolder's mOwnerRecyclerView value to indicate that the ViewHolder is bound to the RV
            holder.mOwnerRecyclerView = RecyclerView.this;
            mAdapter.bindViewHolder(holder, offsetPosition);
            / / if in the pre - process layout, use ViewHolder. MPreLayoutPosition save ViewHolder location on the screen
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
            return true;
        }
        
        public final void bindViewHolder(@NonNull VH holder, int position) {
            // ViewHolder stores the location of the data in the Adapter
            holder.mPosition = position;
            // If each Item has a fixed ID, then the ViewHolder holds that ID
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            // ViewHolder sets the flag FLAG_BOUND
            holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            / / bind ViewHolder
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
            final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if (layoutParams instanceof RecyclerView.LayoutParams) {
                // After binding, mInsetsDirty is set to true, indicating that its ItemDecoration needs to be updated
                ((LayoutParams) layoutParams).mInsetsDirty = true; }}Copy the code

The main thing is to bind a ViewHolder object via the ViewHolder#onBindViewHolder() method. In addition, we need to pay attention to the properties we set to the ViewHolder, some of which may be used when we analyze other procedures.

You need to know how to write a basic Adapter.

Third, after binding the ViewHolder object, you need to make sure that the View you created, ViewHolder#itemView, and its layout parameters are correct, and update some properties, such as the layout parameters that hold the bound ViewHolder object.

If you don’t know the layout parameters, refer to the ViewGroup implementation I wrote for LayoutParams.

Add the child View to the RV

Once you get the subview, you need to add it to the RV

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        // 1. Get the child View
        // 2. Add the subview to the RV
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                // Add the obtained subview to the end of the RV
                addView(view);
            } else{}}}Copy the code

The addView() method is implemented using base LM’s addViewInt(), which is ultimately implemented through McHildhelper.addview ().

    // ChildHelper#addView()
    void addView(View child, int index, boolean hidden) {
        final int offset;
        if (index < 0) {
            // If index is -1, get how many child views the RV already has
            offset = mCallback.getChildCount();
        } else {
            offset = getOffset(index);
        }
        
        // ...
        
        // Add the subview to the RV based on the offset
        mCallback.addView(child, offset);
    }        
Copy the code

Since the index value is -1, the View is added to the end of the RV’s child View.

Measuring the child View

After adding the subview to the RV, you need to measure the subview

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // 1. Get the child View
    // 2. Add the subview to the RV
    // 3. Measure subview
    measureChildWithMargins(view, 0.0);
    // Save the size of LLM consumption in the corresponding direction
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
}    
    
public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    // Get Rect for ItemDecoration
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
    // The Rect of ItemDecoration should be counted
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    // This is a measurement that takes into account the padding, margin, ItemDecoration Rect
    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); }}Copy the code

For the subview measurement, consider the padding, margin, and Rect of ItemDecoration. How to measure it is beyond the scope of analysis in this paper.

Lay out the child View

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        // 1. Get the child View
        // 2. Add the subview to the RV
        // 3. Measure subview
        // 4. Subview layout
        // Calculate the coordinates required for the layout
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
               
            } else{ top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; }}else {
            // ...
        }
        // Do the layout
        layoutDecoratedWithMargins(view, left, top, right, bottom);        
    }
Copy the code

The layout process is very simple, but it’s a little overwritten here.

It is important to understand how custom views measure and lay out. This is the basis for your analysis of this article.

draw

The measurement and layout process has been analyzed, and all that remains is the drawing process

    public void draw(Canvas c) {
        // Call the onDraw() method
        super.draw(c);
        . / / ItemDecoration onDrawOver () to draw
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // Draw the boundary effect
        // ...
    }
    
    public void onDraw(Canvas c) {
        super.onDraw(c);
        
        // Use ItemDecoration. OnDraw ()
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState); }}Copy the code

For the RV, drawing process is mainly drawing ItemDecoration, first is to use ItemDecoration. Ontouch () method for drawing, and then use ItemDecoration. OnDrawOver () to draw.

I’ll use a separate article in this series to explain how ItemDecoration works and how to use it.

feeling

RV source analysis really can not be accomplished overnight, requires great patience and perseverance. This article analyzes the RV process from creation to display in a minimalist (and still long) way, initially looking into the RV principle, but this is far from enough to satisfy my curiosity, and I will continue with this article as a building block.