In view-RecyclerView (2) : ItemDecoration additional LayoutItemDecoration small tools is not applicable for StaggeredGridLayoutManager waterfalls flow layout and also did not deal with the direction of the layout problem. To change the support orientation and StaggeredGridLayoutManager. First attach the code:

/** * Date: 2020/11/28 * author: ice_coffee * Remark: Generic ItemDecoration * @param includeEdge: */ class LayoutItemDecoration(private val spacing: Int, private val includeEdge: Boolean) : RecyclerView. ItemDecoration () {/ * * * position and the corresponding view in column corresponding relation * / private var positionToColumn: MutableMap < Int, Int >? */ private var endLineToColum: IntArray? = null override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: Recyclerview. State) {super.getitemoffsets (outRect, view, parent, State) //item view count = parent. .itemCount ? : 0 / / the current item view adapter position val position. = the parent getChildAdapterPosition (view) the when (val layoutManager = parent.layoutManager) { is GridLayoutManager -> { setGridItemDecoration(layoutManager, outRect, layoutManager.spanCount, position, itemCount) } is StaggeredGridLayoutManager -> { setStaggeredGridItemDecoration(layoutManager, outRect, layoutManager.spanCount, position, itemCount) } is LinearLayoutManager -> { setLinearItemDecoration(outRect, layoutManager.orientation, position, itemCount) } else -> { outRect.set(spacing, spacing, spacing, */ Private fun setLinearItemDecoration(outRect: Rect, orientation: Int, position: Int, itemCount: Int) { if (orientation == LinearLayoutManager.HORIZONTAL) { val top = if (includeEdge) spacing else 0 val bottom = if (includeEdge) spacing else 0 when (position) { 0 -> outRect.set(if (includeEdge) spacing else 0, top, spacing, bottom) itemCount - 1 -> outRect.set(0, top, if (includeEdge) spacing else 0, bottom) else -> outRect.set(0, top, spacing, bottom) } } else { val left = if (includeEdge) spacing else 0 val right = if (includeEdge) spacing else 0 when (position) { 0 -> outRect.set(left, if (includeEdge) spacing else 0, right, spacing) itemCount - 1 -> outRect.set(left, 0, right, if (includeEdge) spacing else 0) else -> outRect.set(left, 0, right, }}} /** * Private fun setGridItemDecoration(layoutManager: GridLayoutManager, outRect: Rect, spanCount: Int, position: Int, itemCount: Int) {/ / the current column val column = position % spanCount calculateGridOutRectByColum (outRect spanCount, itemCount, position, LayoutManager. Orientation, the column)} / flow line layout setting falls * * * * / private fun setStaggeredGridItemDecoration (layoutManager: StaggeredGridLayoutManager, outRect: Rect, spanCount: Int, position: Int, itemCount: Int) { if (null == positionToColumn) { positionToColumn = HashMap() } if (null == endLineToColum) { endLineToColum = IntArray(spanCount)} var column = 0 if (positionToColumn!! .containsKey(position)) { column = positionToColumn!! [position]!! } else { if (position > 0) { val lastViewPosition = position - 1 val lastView = layoutManager.findViewByPosition(lastViewPosition) if (null ! = lastView) { val lastViewColumn = positionToColumn!! [lastViewPosition]!! if (layoutManager.orientation == StaggeredGridLayoutManager.VERTICAL) { endLineToColum!! [lastViewColumn] = endLineToColum!! [lastViewColumn] + lastView.height } else { endLineToColum!! [lastViewColumn] = endLineToColum!! If (position < spanCount) {column = position} else {var minLine = Int.MAX_VALUE for (i in spanCount - 1 downTo 0) { if (endLineToColum!! [i] <= minLine) { minLine = endLineToColum!! [i] column = i } } } positionToColumn!! [position] = column } calculateGridOutRectByColum(outRect, spanCount, itemCount, position, layoutManager.orientation, The column)} / calculation interval * * * * / private fun calculateGridOutRectByColum (outRect: the Rect, spanCount: Int, itemCount: Int, position: Int, orientation: Int, column: Int) {/ / whether needs to include the border if (includeEdge) {if (orientation = = StaggeredGridLayoutManager. VERTICAL) {outRect. Left = spacing - Right = (column + 1) * spanCount { outRect.top = spacing } outRect.bottom = spacing } else { outRect.top = spacing - column * spacing / spanCount If (position < spanCount) {outrect. left = spacing} outRect.right = spacing } } else { if (orientation == StaggeredGridLayoutManager.VERTICAL) { outRect.left = column * Right = spacing - (column + 1) * spanCount outrect. top = 0 // Determine if (position +  spanCount < itemCount) { outRect.bottom = spacing } } else { outRect.top = column * spacing / spanCount outRect.bottom Left = 0 if (position + spanCount < itemCount) { outRect.right = spacing } } } } }Copy the code

Support orientation is simpler, cooperate LayoutManager. Orientation judgment is ok.

StaggeredGridLayoutManager and GridLayoutManager set the most important point is to calculate the current ItemView ItemDeration should be added to the column of the column. The GridLayoutManager is easy to compute colum because its layout is ordered (RTL \ LTR added in sequence) :

val column = position % spanCount
Copy the code

The shortest and StaggeredGridLayoutManager ItemView will be added to the current column. In the code. Navigate directly to the fill() method (the layout logic of the Android provided LayoutManager child View is all in the Fill () method).

Private int fill(RecyclerView.Recycler Recycler, LayoutState LayoutState, RecyclerView.State State) {... while (layoutState.hasMore(state) && (mLayoutState.mInfinite || ! mRemainingSpans.isEmpty())) { View view = layoutState.next(recycler); LayoutParams lp = ((LayoutParams) view.getLayoutParams()); final int position = lp.getViewLayoutPosition(); final int spanIndex = mLazySpanLookup.getSpan(position); Span currentSpan; final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID; if (assignSpan) { currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState); mLazySpanLookup.setSpan(position, currentSpan); if (DEBUG) { Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position); } } else { if (DEBUG) { Log.d(TAG, "using " + spanIndex + " for pos " + position); } currentSpan = mSpans[spanIndex]; }}... }Copy the code

As you can see, fill() adds ItemView to RecyclerView through a while loop. CurrentSpan indicates that views retrieved from LayoutState.next (Recycler) should be added to that column. Next look at getNextSpan() which is an important method to get currentSpan.

private Span getNextSpan(LayoutState layoutState) { final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection); final int startIndex, endIndex, diff; if (preferLastSpan) { startIndex = mSpanCount - 1; endIndex = -1; diff = -1; } else { startIndex = 0; endIndex = mSpanCount; diff = 1; } if (layoutState.mLayoutDirection == LAYOUT_END) { Span min = null; int minLine = Integer.MAX_VALUE; final int defaultLine = mPrimaryOrientation.getStartAfterPadding(); for (int i = startIndex; i ! = endIndex; i += diff) { final Span other = mSpans[i]; int otherLine = other.getEndLine(defaultLine); if (otherLine < minLine) { min = other; minLine = otherLine; } } return min; } else { Span max = null; int maxLine = Integer.MIN_VALUE; final int defaultLine = mPrimaryOrientation.getEndAfterPadding(); for (int i = startIndex; i ! = endIndex; i += diff) { final Span other = mSpans[i]; int otherLine = other.getStartLine(defaultLine); if (otherLine > maxLine) { max = other; maxLine = otherLine; } } return max; }}Copy the code

You can see that the logic in the getNextSpan() method is to iterate through all the columns to find the column with the shortest current length (length represents the total length of the added child elements, depending on the layout direction) and return it. Then there is the Span execution taken by root

  • Add:addView()
  • Measurements:measureChildWithDecorationsAndMargin()
  • Layout:layoutDecoratedWithMargins()

And so on.

Can see StaggeredGridLayoutManager ItemView will be added to the short column, so how to determine the current ItemView should be added to the list? I follow the logic of getNextSpan() to determine the colum value by recording the current length of each column. See the code ^_^ below for the implementation logic.