Add Split Line to RecyclerView

Add Split Line to RecyclerView

Since RecyclerView does not support properties such as divider s, we need to implement them ourselves.

1. Set margin for Item layout to implement

2. Free to draw split lines

There is a second major implementation here

Create Class Inheritance and RecyclerView.ItemDecoration

public class MyItemDecoration extends RecyclerView.ItemDecoration {
    // Draw our own splitter line with a brush
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    }
}

Add Constructor

    private Drawable mDivider;
    // Pass in drawableId defined by ourselves
    public MyItemDecoration(Context context, int drawableResId) {
        mDivider = ContextCompat.getDrawable(context,drawableResId);
        if (mDivider == null){
           throw  new IllegalArgumentException("can not init mDivider Drawable by drawableResId ");
        }
    }

Add the following code to the onDraw () method

@Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            // Draw our horizontal dividing line
            drawHorizontal(c,parent);
            //Draw our numeric dividing line
            drawVertical(c,parent);
    }
Continue
 /**
     * Draw Horizontal Split Line
     */
    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
       canvas.save();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View childView = parent.getChildAt(i);
          RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
            // Draw lines at the bottom of each childView and calculate the position of the split line based on the position of the View
            //  The same is true for the bottom of the subview + the bottomMargin value of the subview
            int top = childView.getBottom()  +  layoutParams.bottomMargin ;
            int  bottom =top+mDivider.getIntrinsicHeight();
             int right =childView.getRight()+layoutParams.rightMargin+mDivider.getIntrinsicWidth();
             int left = childView.getLeft()-layoutParams.leftMargin ;
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    /**
     * Draw a vertical dividing line
     */
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View childView = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
            // Draw lines to the right of each childView
            int top = childView.getTop() -layoutParams.topMargin ;
            int  bottom =childView.getBottom() +layoutParams.bottomMargin ;
             int left = childView.getRight()+ layoutParams.rightMargin ;
             int right =left+mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

The important thing above is the code that was drawn, and the following is an analysis of the getItemOffsets () method;

 @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,RecyclerView.State state) {
        int right = mDivider.getIntrinsicWidth();
        int bottom = mDivider.getIntrinsicHeight();
        // Determine if the current view is the last line 
        if (isLastRow(view,parent)){
            bottom = 0 ;
        }
        // Determine if the current view is the last column 
        if (isLastColumn(view,parent)){
            right = 0 ;
        }
        // You can understand this by setting Padding values around the top and bottom of TextView 
        outRect.bottom = bottom;
        outRect.right = right;
    }

Determine if the current view is the last line isLastRow(view,parent)

  /**
     * Is it the last line
     */
    private boolean isLastRow(View view, RecyclerView parent) {
        //Location of the current subview
        int currentPosition = ((RecyclerView.LayoutParams) 
                       view.getLayoutParams()).getViewLayoutPosition();
        //Total number of subviews
        int itemCount = parent.getAdapter().getItemCount();
        //Number of columns
        int spanCount = getSpanCount(parent);
        //Total 9/3 rows 39/2 rows 5 
        int totalRow =  itemCount % spanCount == 0 ? itemCount / spanCount :(itemCount / spanCount)+1 ;
        return (currentPosition+1) > (totalRow -1 )* spanCount;
    }

Determine if the current view is the last isLastColumn(view,parent)

  /**
     * Is it the last column
     */
    private boolean isLastColumn(View view, RecyclerView parent) {
        //Location of the current subview
        int currentPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        //Number of columns
        int spanCount = getSpanCount(parent);
        return (currentPosition+1) % spanCount == 0;
    }

Get the total number of columns

 private int getSpanCount( RecyclerView parent){
         // Number of columns
        int spanCount = 1;
        LayoutManager layoutManager = parent.getLayoutManager();
        //Judge LayoutManager and get the total number of columns
        if (layoutManager instanceof GridLayoutManager){
            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();

        } else if (layoutManager instanceof StaggeredGridLayoutManager){
            spanCount = ((StaggeredGridLayoutManager) layoutManager)
                    .getSpanCount();
        }
        return spanCount;
    }

Custom drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >     // Set Shape to Rectangle
    <size android:height="50dp" android:width="50dp"/>    Width and Height
    //Gradient color
    <gradient android:startColor="@android:color/holo_blue_bright"   Start color
        android:endColor="@android:color/holo_blue_dark"            End color
        android:centerColor="@android:color/holo_blue_light"        Intermediate color
        />
</shape>

Final Run Results

  1. ListView-like effects

  2. GridView-like effect

problem

When you change the width and height of the shape to 40dp, you can clearly see that the rightmost column is one width wider than the others that we have set in the shape.

Source Code Analysis

In the code that measures the child View in RecyclerView

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

            //A key
            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);

            // Finally, take out the left, top, right, bottom that we set in the getItemOffsets() method
            // These values are then used to measure the width and height of the subView. Getting the width and height values in the getChildMeasureSpec() method actually subtracts the widthUsed and heightUsed values, so the line you draw is actually where the subView is located.
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;
            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
                    canScrollVertically());
            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }
        }

What does this code do?Rectinsets=mRecyclerView.getItemDecorInsetsForChild;

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

        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }

        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
       // mItemDecorations The splitters we added are stored in this collection
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            //Call the method we override on the dividing line, and
            mItemDecorations.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;
    }

In RecyclerView's onDraw(), the subView is drawn first, and the dividing line is drawn last

@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);
        }
    }

Tags: Android xml encoding

Posted on Sun, 17 May 2020 13:24:39 -0400 by LuAn