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

效果如下:

同理,我们也可以使用类似的方式来实现支持 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));

效果如下:

看上去好像没什么问题,但是我们把 mDividerHeight 调大到 50,效果图如下:

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

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

看如下图:

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

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

效果图如下:

github代码地址

去github下载相关代码及实例

更多相关文章

  1. 安卓开发之 在应用中使用数据库
  2. Android中调用Paint的measureText()方法取得字符串显示的宽度值
  3. android创建optionsmenu的方法
  4. Android(安卓)注入Event
  5. Android之Activity的启动方式
  6. Android导入项目后,左上角报错的解决方法
  7. 小白学习android: google code 上源代码的下载方法
  8. View点击涟漪效果
  9. Android(安卓)两种串口实现方法总结

随机推荐

  1. Django:测试成功加载静态文件
  2. 如何将json转换为对象?
  3. 环境变量的安装以及python cahrm的安装以
  4. Python多行正则表达式忽略字符串中的n行
  5. 后端传给前端int 类型数据自增或自减
  6. FP-growth算法思想和其python实现
  7. 在生产中是否应该减少服务器代码?
  8. Python学习手册(第四版)学习笔记(二)我学Pyth
  9. python 按位置关系输出矩阵元素
  10. 基础入门_Python-进线协程.分分钟玩转mul