Android RecyclerView —— 自定义分割线

Android RecyclerView —— 自定义分割线

Android RecyclerView —— 基本使用

Android RecyclerView —— 适配器封装探索

前面说了 RecyclerView 的基本使用以及对适配器的封装,但是在使用 ListView 时,有 dividerHeightdivider 属性用来设置分割线的高度和颜色(Drawable),但是在 RecyclerView 中并没有这个属性,而且也没有替代的属性,也就是说,在 RecyclerView 并我们不能直接设置分割线,而需要使用 RecyclerView 提供的方法 addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) 来增加分割线。 RecyclerView.ItemDecoration 需要由我们自己实现。主要需要实现的有三个方法(真正实现的其实只有2个):

// 在 item 绘制之前调用(就是绘制在 item 的底层) [和 onDrawOver() 方法二选一即可]
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    this.onDraw(c, parent);
}

// 在 item 绘制之后调用(就是绘制在 item 的上层) [和 onDraw() 方法二选一即可]
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    this.onDrawOver(c, parent);
}

// 设置偏移量
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}

getItemOffsets()实际上是在条目之间分配一块矩形区域用来放置我们的分割线,注意一下方法参数Rect outRect,而通过 onDraw()方法或者 onDrawOver() 方法把颜色(Drawable)画在刚刚申请的间隔矩形中;这样就相当于增加了分割线。

提示:在 support 包的版本是 25或以上时,系统提供了一个默认绘制分割线的实现 DividerItemDecoration,但是该实现只针对 LinearLayoutManager 。而对于 GridLayoutManager 并没有默认的实现,所以我们需要自定义实现。

实现简单的分割线类

以下代码实现了一个最简单的分割线类,支持 LinearLayoutManager 类型。

public class LinearDividerDecoration extends RecyclerView.ItemDecoration {
    private int mDividerHeight = 40;
    private int mDividerColor = 0xFFFF0000;
    private Paint mPaint;
    private int mOrientation;

    public LinearDividerDecoration(@RecyclerView.Orientation int orientation) {
        this.mOrientation = orientation;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(mDividerColor);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            outRect.set(0, 0, 0, mDividerHeight);
        } else {
            outRect.set(0, 0, mDividerHeight, 0);
        }
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; ++i) {
            View childAt = parent.getChildAt(i);
            int left = 0;
            int right = parent.getWidth();
            int top = childAt.getBottom() ;
            int bottom = childAt.getBottom()+ mDividerHeight;
            canvas.drawRect(left, top, right, bottom, mPaint);
        }
        canvas.restore();
    }

}

使用:

// 增加分割线
recyclerView.addItemDecoration(new LinearDividerDecoration(RecyclerView.VERTICAL));

效果如下:
Android RecyclerView —— 自定义分割线_第1张图片

同理,我们也可以使用类似的方式来实现支持 GridLayoutManager 的分割线类。

public class GridDividerDecoration extends RecyclerView.ItemDecoration {
    private int mDividerHeight = 4;
    private int mDividerColor = 0xFFFF0000;
    private Paint mPaint;
    private int mOrientation;

    public GridDividerDecoration(@RecyclerView.Orientation int orientation) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(mDividerColor);
        mOrientation = orientation;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
        int itemCount = parent.getAdapter().getItemCount();
        int viewLayoutPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();

        int left = 0;
        int top = 0;
        int right = mDividerHeight;
        int bottom = mDividerHeight;
        if (isLastRow(layoutManager, itemCount, viewLayoutPosition)) {
            // 如果是最后一行,则不需要绘制底部
           bottom = 0;
        }
        if (isLastCol(layoutManager, itemCount, viewLayoutPosition)) {
            // 如果是最后一列,则不需要绘制右边
            right = 0;
        }

        outRect.set(left, top, right, bottom);
    }

    /**
     * 判断是否最后一列
     */
    private boolean isLastCol(GridLayoutManager layoutManager, int childCount, int itemPosition) {
        GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
        int spanCount = layoutManager.getSpanCount();
        int spanIndex = spanSizeLookup.getSpanIndex(itemPosition, spanCount);
        int spanSize = spanSizeLookup.getSpanSize(itemPosition);

        if (mOrientation == GridLayoutManager.VERTICAL) {
            return spanIndex + spanSize == spanCount;
        } else {
            return (childCount - itemPosition) / (spanCount * 1.0f) <= 1;
        }
    }

    /**
     * 判断是否最后一行
     */
    private boolean isLastRow(GridLayoutManager layoutManager, int childCount, int itemPosition) {
        GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
        int spanCount = layoutManager.getSpanCount();
        int spanIndex = spanSizeLookup.getSpanIndex(itemPosition, spanCount);
        int spanSize = spanSizeLookup.getSpanSize(itemPosition);

        if (mOrientation == GridLayoutManager.VERTICAL) {
            return (childCount - itemPosition) / (spanCount * 1.0f) <= 1;
        } else {
            return spanIndex + spanSize == spanCount;
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        c.save();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = parent.getChildAt(i);
            drawHorizontal(c, childAt);
            drawVertical(c, childAt);
        }
        c.restore();
    }

    public void drawHorizontal(Canvas c, View childAt) {
        int left = childAt.getLeft();
        int right = childAt.getRight();
        int top = childAt.getBottom() ;
        int bottom = childAt.getBottom()+ mDividerHeight;
        c.drawRect(left, top, right, bottom, mPaint);
    }

    public void drawVertical(Canvas c, View childAt) {
        int left = childAt.getRight();
        int right = left + mDividerHeight;
        int top = childAt.getTop();
        int bottom = childAt.getBottom() + mDividerHeight;
        c.drawRect(left, top, right, bottom, mPaint);
    }
}

使用:

// 增加分割线
recyclerView.addItemDecoration(new GridDividerDecoration(GridLayoutManager.VERTICAL));

效果如下:
Android RecyclerView —— 自定义分割线_第2张图片

看上去好像没什么问题,但是我们把 mDividerHeight 调大到 50,效果图如下:
Android RecyclerView —— 自定义分割线_第3张图片

上图我们能明显的发现item的宽度不一样了,这是什么原因造成的了,这是因为分割线占用的控件是属于 item 的空间,那么当 GridLayoutManager 设置了 spanCount 时,RecyclerView 就会把整个控件的宽度平均分配成 spanCount 份,每一个item的大小都是一样的,但是因为我们在绘制列分割线时,最后一样没有绘制分割线,所以导致会后一列的item宽度明显大于前面的宽度,从而造成了item宽度不一样的问题。

解决 GridLayoutManager 添加了分割线之后 item 宽度不同的问题

看如下图:
Android RecyclerView —— 自定义分割线_第4张图片

说明:我们把整个item的宽度定义为width,然后左右的分割线宽度为 lw(需要的话),中间的分割线宽度为 dw,那么一共的分割线宽度就是:total = (spanCount - 1) * dw + 2 * lw;这样一来就能计算出每个item需要留出的空间:item = total / spanCount;因为每个item留出的相等的空间,那么item剩余的空间也是相同的了,也就是item的宽度也就是一样的了。

具体的代码实现,请看这里。对 RecyclerView 的分割线进行了封装,实现了自定义设置分割线宽、高,是都需要绘制四周的分割线,以及四周分割线的宽高,还有分割线的颜色、交叉点的颜色等。

效果图如下:
Android RecyclerView —— 自定义分割线_第5张图片

github代码地址

去github下载相关代码及实例

你可能感兴趣的:(Android,控件)