RecycleView自定义分割线

我们在使用ListView的时候可以很轻松的设置列表的分割线,因为系统提供了我们外部调用接口。但是在使用RecycleView是我们会发现并没有直接设置分分割线的方面。不过我们可以自定义一个BaseItemDecoration继承 RecyclerView.ItemDecoration,通过RecycleView的addItemDecoration(ItemDecoration decor)方法进行设置。这里我们主要来看一下 BaseItemDecoration 的实现。

自定义 BaseItemDecoration 类继承自RecyclerView.ItemDecoration,重写其中的 getItemOffsets 和 onDraw 方法。

在 getItemOffsets 方法中,我们针对ItmView的四周边距进行设置,预留出分割线的位置。


    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//         outRect.set(left, top, right, bottom); //在Item的四周设定距离
        int position = parent.getChildAdapterPosition(view);
        view.setTag(position);
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            int orientation = ((LinearLayoutManager) layoutManager).getOrientation();
            if (layoutManager instanceof GridLayoutManager) {
                GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
                int spanCount = gridLayoutManager.getSpanCount();
                mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, spanCount);
                //占用几个单元
                int spanSize = spanSizeLookup.getSpanSize(position);
                //在一行/列中的index
                int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
                //所在行/列
                int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);

                if (orientation == LinearLayoutManager.VERTICAL) {
                    //不是第一个
                    if (spanSize < spanCount && spanIndex != 0) {
                        // 左 偏移
                        outRect.left = mHeightVertical;
                    }
                    //非最后行
                    if (spanGroupIndex < mMaxSpanGroupIndex) {
                        // 下 偏移
                        outRect.bottom = mHeightHorizontal;
                    }
                } else {
                    //不是第一个
                    if (spanSize < spanCount && spanIndex != 0) {
                        // 上 偏移
                        outRect.top = mHeightHorizontal;
                    }
                    //非最后列
                    if (spanGroupIndex < mMaxSpanGroupIndex) {
                        // 右 偏移
                        outRect.right = mHeightVertical;
                    }
                }
            } else {
                if (position != 0) {
                    if (position >= mStartPosition) {
                        if (orientation == LinearLayoutManager.VERTICAL) {
                            outRect.top = mHeightHorizontal;
                        } else {
                            outRect.left = mHeightVertical;
                        }
                    }
                }
            }
        }
    }

在 onDraw 中进行分割线的绘制,此处的绘制在ItemView的绘制之前;也可再 onDrawOver 中进行绘制,但分割线会覆盖在ItemVIew的布局只是。因此推荐使用 onDraw。

下面我们可以看下整个重写的 BaseItemDecoration

public class YTBaseItemDecoration extends RecyclerView.ItemDecoration {
    /**
     * 分割线尺寸
     * 注意:这里的水平竖向是针对屏幕而言
     * 水平分割线高度竖向分割线高度
     */
    private int mHeightHorizontal, mHeightVertical;
    /**
     * 最大多少行/列
     */
    private int mMaxSpanGroupIndex;
    private Paint mPaint;

    /**
     * 从特定位置开始绘制,只对 LinearLayoutManager 横行/竖向分割线有效,网格grid无效
     */
    private int mStartPosition = 0;
    /**
     * margin配置,只对 LinearLayoutManager 横行/竖向生效,网格grid无效
     */
    private int mMarginLeft, mMarginRight;

    /**
     * 构造1
     *
     * @param height 分割线尺寸
     */
    public YTBaseItemDecoration(int height) {
        mHeightHorizontal = height;
        mHeightVertical = height;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.TRANSPARENT);
        mPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 构造2
     *
     * @param height 分割线尺寸
     * @param color  分割线填充色
     */
    public YTBaseItemDecoration(int height, @ColorInt int color) {
        mHeightHorizontal = height;
        mHeightVertical = height;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(color);
        mPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 构造3
     *
     * @param height 分割线尺寸
     * @param color  分割线填充色
     * @param margin margin
     */
    public YTBaseItemDecoration(int height, @ColorInt int color, int margin) {
        mHeightHorizontal = height;
        mHeightVertical = height;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(color);
        mPaint.setStyle(Paint.Style.FILL);
        mMarginLeft = margin;
        mMarginRight = margin;
    }

    /**
     * 设置填充色
     *
     * @param color 分割线色值
     * @return YTBaseItemDecoration
     */
    public YTBaseItemDecoration setColor(@ColorInt int color) {
        mPaint.setColor(color);
        return this;
    }

    /**
     * 设置分割线尺寸
     *
     * @param height 尺寸
     * @return YTBaseItemDecoration
     */
    public YTBaseItemDecoration setHeight(int height) {
        mHeightHorizontal = height;
        mHeightVertical = height;
        return this;
    }

    /**
     * 设置分割线从固定position开始绘制
     *
     * @param start
     * @return
     */
    public YTBaseItemDecoration setStartPosition(int start) {
        mStartPosition = start;
        return this;
    }

    /**
     * margin
     *
     * @param margin
     * @return
     */
    public YTBaseItemDecoration setMargin(int margin) {
        mMarginLeft = margin;
        mMarginRight = margin;
        return this;
    }

    /**
     * margin
     *
     * @param marginLeft
     * @param marginRight
     * @return
     */
    public YTBaseItemDecoration setMargin(int marginLeft, int marginRight) {
        mMarginLeft = marginLeft;
        mMarginRight = marginRight;
        return this;
    }

    /**
     * 设置分割线尺寸,主要用于grid横竖方向分割线粗细不一致
     *
     * @param heightHorizontal 水平方向尺寸
     * @param heightVertical   竖直方向尺寸
     * @return
     */
    public YTBaseItemDecoration setHeight(int heightHorizontal, int heightVertical) {
        mHeightHorizontal = heightHorizontal;
        mHeightVertical = heightVertical;
        return this;
    }

    @Override
    public void onDraw(@NotNull Canvas canvas, @NotNull RecyclerView parent, @NotNull RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            drawSpace(canvas, parent);
        } else if (layoutManager instanceof LinearLayoutManager) {
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            if (linearLayoutManager.getOrientation() == RecyclerView.VERTICAL) {
                drawVertical(canvas, parent);
            } else {
                drawHorizontal(canvas, parent);
            }
        }
    }

    private final Rect mBounds = new Rect();

    /**
     * LinearLayoutManager.VERTICAL 绘制水平分割线
     *
     * @param canvas
     * @param parent
     */
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        int left, right, top, bottom;
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        }

        int childCount = parent.getChildCount();
        for (int i = 1; i < childCount; ++i) {
            if (i > mStartPosition) {
                View child = parent.getChildAt(i);
                parent.getDecoratedBoundsWithMargins(child, this.mBounds);
                left = mBounds.left + mMarginLeft;
                right = mBounds.right - mMarginRight;
                top = mBounds.top;
                bottom = top + mHeightHorizontal;
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    /**
     * LinearLayoutManager.HORIZONTAL 绘制竖直分割线
     *
     * @param canvas
     * @param parent
     */
    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        int left, right, top, bottom;
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        }

        int childCount = parent.getChildCount();
        for (int i = 1; i < childCount; ++i) {
            if (i > mStartPosition) {
                View child = parent.getChildAt(i);
                parent.getDecoratedBoundsWithMargins(child, mBounds);
                left = mBounds.left;
                right = left + mHeightVertical;
                top = mBounds.top + mMarginLeft;
                bottom = mBounds.bottom - mMarginRight;
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    /**
     * GridLayoutManager 分割线
     *
     * @param canvas
     * @param parent
     */
    private void drawSpace(Canvas canvas, RecyclerView parent) {
        int top, bottom, left, right;
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            top = parent.getPaddingTop();
            right = parent.getWidth() - parent.getPaddingRight();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(left, top, right, bottom);
        }


        GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
        int spanCount = gridLayoutManager.getSpanCount();
        GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            // 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得
            View child = parent.getChildAt(i);
            int position = (int) child.getTag();
            parent.getDecoratedBoundsWithMargins(child, this.mBounds);
            //所在行/列
            int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
            //item占用单元数
            int spanSize = spanSizeLookup.getSpanSize(position);
            //在行/列中的index
            int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
            if (gridLayoutManager.getOrientation() == RecyclerView.VERTICAL) {
                //预留空间为左+下
                // 非每行第1个,绘制 left 竖向分割
                if (spanIndex != 0 && spanSize < spanCount) {
                    left = mBounds.left;
                    right = left + mHeightVertical;
                    top = mBounds.top + mMarginLeft;
                    if (spanGroupIndex == mMaxSpanGroupIndex) {
                        //最后一行未预留bottom空间,不考虑重叠
                        bottom = mBounds.bottom - mMarginRight;
                    } else if (mMarginRight > 0) {
                        //存在margin时需要考虑重叠影响,减去margin及水平分割线尺寸,
                        bottom = mBounds.bottom - mMarginRight - mHeightHorizontal;
                    } else {
                        bottom = mBounds.bottom;
                    }
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                //非最后行,绘制 bottom 横向分割
                if (spanGroupIndex < mMaxSpanGroupIndex) {
                    if (spanIndex == 0) {
                        //每行第一个左侧无预留空间,不考虑重叠问题
                        left = mBounds.left + mMarginLeft;
                    } else if (mMarginLeft > 0) {
                        //处理margin与水平线重叠
                        left = mBounds.left + mMarginLeft + mHeightVertical;
                    } else {
                        left = mBounds.left;
                    }
                    right = mBounds.right - mMarginRight;
                    bottom = mBounds.bottom;
                    top = bottom - mHeightHorizontal;
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                //最后一个若未撑满最后一行,则需要绘制右侧分割线,
                // 最后一项未预留空间,无需考虑margin与分割线重叠问题
                if (position == parent.getAdapter().getItemCount() - 1) {
                    //计算最后一行所有item是否占满空间,若未满则增加右偏移绘制分割线
                    int span = spanCount;
                    for (int pos = position; pos <= position; pos--) {
                        span = span - spanSizeLookup.getSpanSize(pos);
                        int index = spanSizeLookup.getSpanIndex(pos, spanCount);
                        if (index == 0) {
                            break;
                        }
                    }
                    //最后一行/列未满
                    if (span > 0) {
                        // 右 偏移
                        left = mBounds.right;
                        right = mBounds.right + mHeightVertical;
                        top = mBounds.top + mMarginLeft;
                        bottom = mBounds.bottom - mMarginRight;
                        canvas.drawRect(left, top, right, bottom, mPaint);
                    }
                }
            } else {
                //预留空间为上+右
                // 非每列第1个,绘制 top 分割
                if (spanSize < spanCount && spanIndex != 0) {
                    left = mBounds.left + mMarginLeft;
                    if (spanGroupIndex == mMaxSpanGroupIndex) {
                        //最后一列未预留right空间,不考虑重叠
                        right = mBounds.right - mMarginRight;
                    } else if (mMarginRight > 0) {
                        //存在margin时需要考虑重叠影响,减去margin及竖向分割线尺寸,
                        right = mBounds.right - mMarginRight - mHeightVertical;
                    } else {
                        right = mBounds.right;
                    }
                    top = mBounds.top;
                    bottom = top + mHeightHorizontal;
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                //非最后列,绘制 right 分割
                if (spanGroupIndex < mMaxSpanGroupIndex) {
                    left = mBounds.right - mHeightVertical;
                    right = mBounds.right;
                    if (spanIndex == 0) {
                        //每列第一个右侧无预留空间,不考虑重叠问题
                        top = mBounds.top + mMarginLeft;
                    } else if (mMarginLeft > 0) {
                        //处理margin与竖向线重叠
                        top = mBounds.top + mMarginLeft + mHeightHorizontal;
                    } else {
                        top = mBounds.top;
                    }
                    bottom = mBounds.bottom - mMarginRight;
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                //最后一个若未撑满最后一行,则需要绘制下侧分割线
                // 最后一项未预留空间,无需考虑margin与分割线重叠问题
                if (position == parent.getAdapter().getItemCount() - 1) {
                    //计算最后一行所有item是否占满空间,若未满则增加右偏移绘制分割线
                    int span = spanCount;
                    for (int pos = position; pos <= position; pos--) {
                        span = span - spanSizeLookup.getSpanSize(pos);
                        int index = spanSizeLookup.getSpanIndex(pos, spanCount);
                        if (index == 0) {
                            break;
                        }
                    }
                    if (span > 0) {
                        left = mBounds.left + mMarginLeft;
                        right = mBounds.right - mMarginRight;
                        top = mBounds.bottom;
                        bottom = top + mHeightHorizontal;
                        canvas.drawRect(left, top, right, bottom, mPaint);
                    }
                }
            }
        }
    }

此处针对该自定义类再做说明,其中的mIsShowTopDivider, mIsShowBottomDivider, mIsShowLeftDivider, mIsShowRightDivider 均对应直观意义上的上下左右,RecyclerView 纵滑 则底部为bottom,横滑则底部问right ,请自行脑补。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值