After Android L came out, Google recommended the use of RecyclerView to replace ListView in the development project, because RecyclerView’s flexibility and performance are stronger than ListView, but it also brings many problems, such as: List division line should be controlled by the developers themselves. Moreover, the measurement and layout logic of RecyclerView are entrusted to their LayoutManager to deal with. If the RecyclerView needs to be transformed, the corresponding LayoutManager should be customized. This paper mainly gives RecyclerView use reference to the following scenarios:

Several common scenarios of RecyclerView

  • How to realize the list RecyclerView with dividing line
  • How to realize RecyclerView format with dividing line
  • How to implement fully expanded list RecyclerView(e.g., nested in ScrollView)
  • RecyclerView how to use fully expanded web format RecyclerView(e.g. nested in ScrollView)

First, let’s take a look at the implementation style. In order to facilitate control, no dividing line is set on the boundary, which is convenient for customization. Padding or Margin can be used if necessary. Making connections RecyclerItemDecoration





Web format list style





A fully expanded list of web formats





A fully expanded linear list

Different scenes RecyclerView realization

Default vertical list type RecyclerView

First look at the simplest vertical linear RecyclerView, generally with the following code:

    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    mRecyclerView.setLayoutManager(linearLayoutManager);Copy the code

The above is the simplest realization of linear RecyclerView, but the default does not have a dividing line. If you want to use black 20dp as a dividing line, you need to customize yourself. Google provides ItemDecoration for RecyclerView. It is used to add some ancillary information to the Item, such as: separator line, float layer, etc.

List RecyclerView with dividing line –LinearItemDecoration

RecyclerView provides addItemDecoration interface and ItemDecoration class to customize the separation line style, then, in RecyclerView source code, how to use ItemDecoration? Consistent with the drawing process of ordinary View, RecyclerView also needs to go through measure->layout->draw, and after measure and layout, it should move space for RecyclerView division line according to the restrictions of ItemDecoration. Measure and Layout of RecyclerView are actually entrusted to their own LayoutManager. The measureChildWithMargins function of RecyclerView is called directly or indirectly to the LinearLayoutManager to measure or create layouts. MeasureChildWithMargins will find the ItemDecoration added by addItemDecoration, and the measureChildWithMargins function getItemOffsets the desired space.

  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); }}Copy the code

Visible measureChildWithMargins will first each child’s calculated through getItemDecorInsetsForChild ItemDecoration limited by the boundary of the information, Measure (widthSpec, heightSpec); measure(widthSpec, heightSpec); Look at the getItemDecorInsetsForChild function:

Rect getItemDecorInsetsForChild(View child) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if(! lp.mInsetsDirty) {return lp.mDecorInsets;
    }

    final Rect insets = lp.mDecorInsets;
    insets.set(0.0.0.0);
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0.0.0.0); <! Get (I). GetItemOffsets (mTempRect, child,this, mState);
        insets.left += mTempRect.left;
        insets.top += mTempRect.top;
        insets.right += mTempRect.right;
        insets.bottom += mTempRect.bottom;
    }
    lp.mInsetsDirty = false;
    return insets;
}Copy the code

For a linear layout list, you can simply set a custom ItemDecoration. The outRect parameter mainly controls the width and height of the upper, lower, left, and right dividing lines of each Item. This size should correspond to the size of the LinearItemDecoration (if it needs to be drawn).

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if(mOrientation == VERTICAL_LIST) { <! -- Vertical direction, no padding on the last one -->if (parent.getChildAdapterPosition(view) < parent.getAdapter().getItemCount()1) {
            outRect.set(0.0.0, mSpanSpace);
        } else {
            outRect.set(0.0.0.0); }}else{<! -- Horizontal, no padding on the last one -->if (parent.getChildAdapterPosition(view) < parent.getAdapter().getItemCount()1) {
            outRect.set(0.0, mSpanSpace, 0);
        } else {
            outRect.set(0.0.0.0); }}}Copy the code

After measure and layout, take a look at the onDraw function of RecyclerView. RecyclerView will call the onDraw of ItemDecoration in the onDraw function to draw the dividing line or other auxiliary information. ItemDecoration supports customization of space dividing lines and other information in four directions, the specific style and position to be drawn are completely determined by the developer, so the freedom is very large. In fact, if there are not too special requirements, the onDraw function can do nothing. Just use the background color to achieve the purpose of simple dividing lines, of course, if you want to customize some special patterns, you need to draw your own, look at the onDraw of the LinearItemDecoration.

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (mOrientation == VERTICAL_LIST) {
        drawVertical(c, parent);
    } else{... }}Copy the code

In fact, if there is no special drawing requirement, such as the display of multicolor or pictures, no drawing is required at all. If you must draw, note that the size of the drawing area is the same as that of the original getItemOffsets. If the size of the drawing area is too large, it will not be displayed and will cause the problem of overdrawing:

public void drawVertical(Canvas c, RecyclerView parent) {                               int totalCount = parent.getAdapter().getItemCount();               final int childCount = parent.getChildCount();               for (int i = 0; i < childCount; i++) {                   final View child = parent.getChildAt(i);                   final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child                           .getLayoutParams();                   final int top = child.getBottom() + params.bottomMargin +                           Math.round(ViewCompat.getTranslationY(child));                   final int bottom = top + mVerticalSpan;                           final int left = child.getLeft() + params.leftMargin;                   final int right = child.getRight() + params.rightMargin;                           if(! isLastRaw(parent, i, mSpanCount, totalCount))if(childCounti > mSpanCount) { drawable.setBounds(left, top, right, bottom); drawable.draw(c); }}}Copy the code

Take a quick look at the flow chart





RecyclerView ItemDocration drawing

Network format RecyclerView with dividing line -GridLayoutItemDecoration

The process of RecyclerView network format is similar to the linear list above, but the network format needs to set up a good margin according to the position of each Item, such as the most left side does not need the left side of the placeholder, the most right side does not need the right side of the placeholder, the last line does not need the bottom of the placeholder, as shown below





Restrictions for ItemDocration

Each childView of RecyclerView will set its ItemDecoration through getItemOffsets. For RecyclerView of web format, its ItemDecoration needs to be restricted in four directions. GetItemOffsets: GridLayoutItemDecoration getItemOffsets

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    final int position = parent.getChildAdapterPosition(view);
    final int totalCount = parent.getAdapter().getItemCount();
    int left = (position % mSpanCount == 0)?0 : mHorizonSpan;
    int bottom = ((position + 1) % mSpanCount == 0)?0 : mVerticalSpan;
    if (isVertical(parent)) {
        if(! isLastRaw(parent, position, mSpanCount, totalCount)) { outRect.set(left, 0.0, mVerticalSpan);
        } else {
            outRect.set(left, 0.0.0); }}else {
        if(! isLastColumn(parent, position, mSpanCount, totalCount)) { outRect.set(0.0, mHorizonSpan, bottom);
        } else {
            outRect.set(0.0.0, bottom); }}}Copy the code

In fact, the above code is based on the direction of RecyclerView sliding (horizontal or vertical) and the position of the child (is not the last line or the last column), the restrictions on the attached area, similarly, if not special secant line style, through the background can basically achieve the requirements, without special draw.

All of the type RecyclerView – ExpandedLinearLayoutManager list

RecyclerView full expansion logic is different from the division line, full expansion is mainly related to measure logic, a simple look at RecyclerView (V-22 version, relatively simple) measure source code:

@Override
protected void onMeasure(int widthSpec, int heightSpec) {... <! If mLayout (LayoutManager) is not empty, use mLayoutManager's mLayout.onMeasure.if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
    } else {
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    }

    mState.mInPreLayout = false; // clear
}Copy the code

As can be seen from the above code, after setting the LayoutManager for RecyclerView, the measure logic of RecyclerView is actually entrusted to its LayoutManager. Here takes the LinearLayoutManager as an example. But the LinearLayoutManager source code does not rewrite the onMeasure function, that is, for RecyclerView linear style, for size processing is the same as the ViewGroup, completely restricted by the parent control, but for V-23 inside there are some modifications, Support for WRAP_content has been added. In this case, we can put the setting time of size into the onMeasure of LayoutManager. For the fully expanded RecyclerView, it actually measures all children once, and then adds the height or width required by each child. Have a look at ExpandedLinearLayoutManager implementation: When measuring the child, using RecyclerView measureChildWithMargins, this function has to take into account the placeholder ItemDecoration, really need to take up the size of the obtained through getDecoratedMeasuredWidth after.

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int measureWidth = 0;
    int measureHeight = 0;
    int count;
    if (mMaxItemCount < 0 || getItemCount() < mMaxItemCount) {
        count = getItemCount();
    } else {
        count = mMaxItemCount;
    }
    for (int i = 0; i < count; i++) {
        int[] measuredDimension = getChildDimension(recycler, i);
        if(measuredDimension == null || measuredDimension.length ! =2)
            return;
        if (getOrientation() == HORIZONTAL) {
            measureWidth = measureWidth + measuredDimension[0]; <! MeasureHeight = measureHeight (measureHeight, measuredDimension[)1]);
        } else {
            measureHeight = measureHeight + measuredDimension[1]; <! MeasureWidth = Math. Max (measureWidth, measuredDimension[)0]);
        }
    }

    measureHeight = heightMode == View.MeasureSpec.EXACTLY ? heightSize : measureHeight;
    measureWidth = widthMode == View.MeasureSpec.EXACTLY ? widthSize : measureWidth;
    if (getOrientation() == VERTICAL && measureWidth > widthSize) {
        measureWidth = widthSize;
    } else if (getOrientation() == HORIZONTAL && measureHeight > heightSize) {
        measureHeight = heightSize;
    }
    setMeasuredDimension(measureWidth, measureHeight);
}


private int[] getChildDimension(RecyclerView.Recycler recycler, int position) {
    try {
        int[] measuredDimension = new int[2];
        View view = recycler.getViewForPosition(position);
        // Measure childView to get width and height (including ItemDecoration)
        super.measureChildWithMargins(view, 0.0);
        // Get childView to get width and height (including ItemDecoration limits), and margin
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
        measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
        return measuredDimension;
    } catch (Exception e) {
        Log.d("LayoutManager", e.toString());
    }
    return null;
}Copy the code

All of the format RecyclerView – ExpandedGridLayoutManager net

The realization of fully expanded RecyclerView is very similar to linear. The only difference is that when determining the size, it is not to superimpose the size of each child, but to superimpose the size of each row or column. Here, it is assumed that the row height or column width are the same. Look at the following code, in fact, in addition to the row and column judgment logic, the other basic with the full expansion of linear similar.

 @Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int measureWidth = 0;
    int measureHeight = 0;
    int count = getItemCount();
    int span = getSpanCount();
    for (int i = 0; i < count; i++) {
        measuredDimension = getChildDimension(recycler, i);
        if (getOrientation() == HORIZONTAL) {
            if (i % span == 0 ) {
                measureWidth = measureWidth + measuredDimension[0];
            }
            measureHeight = Math.max(measureHeight, measuredDimension[1]);
        } else {
            if (i % span == 0) {
                measureHeight = measureHeight + measuredDimension[1];
            }
            measureWidth = Math.max(measureWidth, measuredDimension[0]);
        }
    }
    measureHeight = heightMode == View.MeasureSpec.EXACTLY ? heightSize : measureHeight;
    measureWidth = widthMode == View.MeasureSpec.EXACTLY ? widthSize : measureWidth;
    setMeasuredDimension(measureWidth, measureHeight);
}Copy the code

Finally, the lateral sliding effect is attached:





The horizontal sliding

The above is the more general RecyclerView use scenario and compatibility, finally attached Github link RecyclerItemDecoration, welcome star, fork.