The LinearLayoutManager is not a View, but a utility class, but the LinearLayoutManager takes care of the layout, measurement, reuse, recycle, cache rolling and so on of a View.

One, recall

Android Render(three)supportVersion 27.0.0 source code RecyclerView drawing process analysis has said the RecyclerView drawing process, DispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3 Only when the width and height of RecyclerView is dead or match_parent, dispatchLayoutStep1 dispatchLayoutStep2 will be executed in advance. Step3 of dispatchLayoutStep3 is performed in the onLayout phase. In RecyclerView write dead width height when onMeasure phase is easy, directly set width height. However, in onLayout stage, dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3 will be executed successively.


1 – LayoutManager draws three steps

Second, onLayoutChildren began the layout preparation work

The above figure is the invocation of three methods of dispatchLayoutStep in RecyclerView. The onLayoutChildren method of LayoutManager is called in the dispatchLayoutStep2 method to lay out the ItemView.

private void dispatchLayoutStep2() { ...... // Step 2: Run layout mState. MInPreLayout = false; // Call the 'onLayoutChildren' method of 'LayoutManager' to lay out 'ItemView' mlayout. onLayoutChildren(mRecycler, mState); . Slightly}Copy the code

The LinearLayoutManager circulates the layout of all ItemViews.


2-LinearLayOutManager drawing analysis

Although there will be three steps in RecyclerView source code rendering processing, but are not really do the drawing layout measurement, the real drawing layout measurement are placed in different LayoutManager, we will take the LinearLayoutManager as an example to analyze. Of the three layoutManagers, LinearLayoutManager should be the simplest. GridLayoutManager also inherits the LinearLayoutManager implementation, but implements a different layout in the layoutChunk method.

The LinearLayoutManager layout starts with the onLayoutChildren method:

//LinearLayoutManager layout starts from onLayoutChildren @override public void onLayoutChildren(RecyclerView.Recycler Recycler, RecyclerView.State state) { // layout algorithm: Checking children and other variables // Find an anchor coordinate and an anchor item position. Locate the anchor coordinates and anchor point project location mAnchor for the layout anchor point understood as a starting point that does not have. // MAnchor contains the sub-control's initial drawing offset (coordinate) on the Y axis,ItemView's position and mLayoutFromEnd in the Adapter // 2) Fill towards start, // 3) fill towards end, stacking from bottom, stacking from bottom // 4) Scroll to fulfill like stack from bottom. Scroll to meet the requirements of the stack from the bottom...... Slightly ensureLayoutState (); mLayoutState.mRecycle = false; / / resolve layout direction set layout direction (VERTICAL/HORIZONTAL) resolveShouldLayoutReverse (); // Reset the drawing anchor information manchorinfo.reset (); // mStackFromEnd requires us to actively call, Or has not been false / / VERTICAL direction for mLayoutFromEnd to false HORIZONTAL direction is to true mAnchorInfo. MLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; Calculate Anchor position and coordinate // ====== Calculation save map anchor updates updateAnchorInfoForLayout (recycler, state, mAnchorInfo); . Slightly / / HORIZONTAL direction started to draw the if (mAnchorInfo. MLayoutFromEnd) {/ / = = = = = = layout algorithm step 2 = = = = = = : The fill forward start anchor position towards the start filling ItemView updateLayoutStateToFillStart (mAnchorInfo); mLayoutState.mExtra = extraForStart; // Fill the recycler (mLayoutState, state, false) for the first time; startOffset = mLayoutState.mOffset; final int firstElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } / / = = = = = = layout algorithm step 3 = = = = = = : the fill forward end anchor position towards the end of filling ItemView updateLayoutStateToFillEnd (mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; // Fill the recycler (mLayoutState, state, false) second time; endOffset = mLayoutState.mOffset; . Slightly} else {/ / VERTICAL direction start drawing / / = = = = = = layout algorithm step 2 = = = = = = : The fill forward end anchor position towards the end of filling ItemView updateLayoutStateToFillEnd (mAnchorInfo); mLayoutState.mExtra = extraForEnd; // Fill the recycler (mLayoutState, state, false) for the first time; endOffset = mLayoutState.mOffset; final int lastElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForStart += mLayoutState.mAvailable; } / / = = = = = = layout algorithm step 3 = = = = = = : the fill forward start anchor position towards the start filling ItemView updateLayoutStateToFillStart (mAnchorInfo); mLayoutState.mExtra = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; // Fill the recycler (mLayoutState, state, false) second time; startOffset = mLayoutState.mOffset; . } // === Layout algorithm step 4 === : Scroll offset calculation, if necessary will invoke the fill method to fill in the new ItemView layoutForPredictiveAnimations (recycler, state, startOffset, endOffset); }Copy the code

Layout algorithm:

  • 1. By checking the child and other variables, find the anchor coordinates and anchor point item location mAnchor for the starting point that the layout anchor points understand as not having, MAnchor contains the initial drawing offset of the child control on the Y axis, the position of the ItemView in the Adapter and the layout direction (mLayoutFromEnd).
  • 2. Start filling, stacking from the bottom
  • 3. Finish filling and stack from the top
  • 4. Scroll to meet stack requirements from the bottom

I’ve marked all four steps in the code.

For why we call the fill method several times, formEnd, formStart, see the picture:




3 – RecyclerView ItemView filling direction

Schematic diagram source: blog.csdn.net/qq_23012315…

Round red dot in the first step is our layout algorithm updateAnchorInfoForLayout method calculated fill the anchor position.

In the first case, the display position of the screen is at the bottom of RecyclerView, so there is only one filling direction for formEnd

In the second case, the screen is displayed at the top of RecyclerView, so there is only one filling direction for formStart

The third case is the most common. The screen is in the middle of RecyclerView, so the fill direction is formEnd and formStart. This is why the fill method is called twice.

The above is the case where the direction of RecyclerView is VERTICAL, and the filling algorithm is unchanged when the direction is HORIZONTAL.

Fill starts the layout of ItemView

The fill core is a while loop that executes a core method:

LayoutChunk, which populates an ItemView to the screen once executed.

Take a look at the code for the fill method:

// Fill method returns the number of pixels needed to fill the ItemView. Int fill(RecyclerView.Recycler Recycler, LayoutState LayoutState, RecyclerView.State State, Boolean stopOnFocusable) {// Fill the initial position final int start = layoutstate.mavailable; if (layoutState.mScrollingOffset ! // Recycle recycleByLayoutState(Recycler, LayoutState); // Recycle recycler (recycler, LayoutState); } // Calculate the remaining padding space int remainingSpace = layOutstate.mavailable + layoutstate.mextra; LayoutChunkResult LayoutChunkResult = mLayoutChunkResult; / / = = = = = = = = = = = = = = = = = = core while loop = = = = = = = = = = = = = = = = = = = = while ((layoutState. MInfinite | | remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); // ====== Fill itemView core fill method ====== Continue layoutChunk(Recycler, State, layoutState, layoutChunkResult) if screen space is available and data is available; }... Return start-layoutstate. MAvailable; return start-layoutstate. }Copy the code

The code looks pretty clean. The explanations are annotated and will not be listed. We see here that the core method for the next step in fill is layoutChunk, which is executed once to populate an ItemView.

LayoutChunk creates a fill measurement layout ItemView

The layoutChunk method has four steps: Create, populate, measure, and layout an ItemView:

  • 1 layoutState.next(recycler)Method from the primary or secondary cache or create oneItemView.
  • 2 addViewMethod adds aItemViewtoViewGroupIn the.
  • 3 measureChildWithMarginsMethod Measure aItemView.
  • 4 layoutDecoratedWithMarginsMethod layout aItemView. The layout will calculate the left, top, right, and bottom positions of an ItemView.

These are the four key steps:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {// ====== step 1 ====== Retrieve or create an ItemView View from the first or second cache = LayoutState. next(recycler); if (view == null) { if (DEBUG && layoutState.mScrapList == null) { throw new RuntimeException("received null view when unexpected"); } // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.mFinished = true; return; } // ====== step 2 ====== add ItemV as appropriate, 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); MeasureChildWithMargins (View, 0, 0); // ====== step 3 ====== Measure the size of an ItemView with its margin value measureChildWithMargins(View, 0, 0); result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); Int left, top, right, bottom; int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { top = getPaddingTop(); bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { right = layoutState.mOffset; left = layoutState.mOffset - result.mConsumed; } else { left = layoutState.mOffset; right = layoutState.mOffset + result.mConsumed; } } // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct Layout position, we subtract from an ItemView left, Top, right, Bottom coordinates to determine its position / / step 4 = = = = = = = = = = = = to determine the position of a ItemView layoutDecoratedWithMargins (view, left, top, right, bottom); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); } // Consume the available space if the view is not removed OR changed if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.hasFocusable(); }Copy the code

LinearLayoutManager filling, measuring, layout process summary

From the beginning of RecyclerView drawing trigger, will need to draw the ItemView to do a while loop drawing once, the middle to go through a number of steps, but also design to cache. RecyclerView drawing processing is still more complex.