写一个万用RecyclerView分隔线,支持linear grid staggered

本文介绍了如何创建一个适用于大多数场景的通用RecyclerViewItemDecoration,支持linear、grid和staggered布局管理器,以及横竖向和跨列的分隔线设置。代码简洁,易于复用,还提供了示例效果图。文章强调了良好的编程习惯和架构师应具备的能力。
摘要由CSDN通过智能技术生成

前言

2023已过半,才发现我已经大半年没写博客了,痛定思痛决定水一篇。

不知道大家平时干活的时候有没有被RecyclerView列表的分隔线困扰过,app里一般都会有各种各样的列表,横的竖的、网格、瀑布流样式的,每次要给列表设分隔线时都要自己写一个特化的ItemDecoration,既麻烦又难以复用,那能不能写一个适用大多数场景的ItemDecoration来减轻这类负担呢?

别急,本篇文章就给大家带来一个我自用的通用ItemDecoration,支持linear grid staggered LayoutManager,支持横竖向、跨列等情况;支持边缘、横纵向分隔线不同宽度,使用也非常简单。

效果图和代码

代码

单个类可以直接使用,仓库包含demo

效果图 网格样式

20230621_173920.gif

瀑布流

20230621_174043.gif

这个ItemDecoration暂时没有实现分隔线上色,因为我觉得这种场景其实很少就把相关代码删掉了,要加的话建议通过继承实现。

实现和注意点

首先,由于要支持横竖向,所以定义两个轴,主轴代表可滑动的那个轴,交叉轴代表另一个轴,这样无论是横向还是竖向都能保持语义一致

// 主轴方向分割线宽度
protected var mainWidth = 0

// 交叉轴方向分割线宽度
protected var crossWidth = 0

// 边缘宽度
protected var mainPadding = 0
protected var crossPadding = 0

主轴的间隔

主轴的分隔线很简单,第一行的item和最后一行的item设置边缘间隔,其他每个item在主轴同一方向上设置分隔线间隔,关键点在于首行和末行的判断。

LinearLayoutManager情况下最简单,判断position是首个或者最后一个就ok了,但是GridLayoutManager和StaggeredGridLayoutManager都存在跨列问题。比如说列表有5列,但是第一个item就占满了整行,那么本该在第一行的2-5个item实际上就不在第一行了;末行判断同理。

GridLayoutManager通过它的SpanSizeLookup来判断,groupIndex0在首行,groupIndexlastGroupIndex在最后一行

// 当前item在哪一行
val groupIndex = manager.spanSizeLookup.getSpanGroupIndex(position, spanCount)
// 最后一个item在哪一行
val lastGroupIndex = manager.spanSizeLookup.getSpanGroupIndex(size - 1, spanCount)

StaggeredGridLayoutManager相对麻烦一些,看下面的注释,spanIndex代表当前item在本行内的下标

val lp = view.layoutParams
if (lp is StaggeredGridLayoutManager.LayoutParams) {
    val spanCount = manager.spanCount
    // 前面没有跨列item时当前item的期望下标
    val exceptSpanIndex = position % spanCount
    // 真实的item下标
    val spanIndex = lp.spanIndex
    // position原属于第一行并且此item之前没有跨列的情况,当前item才属于第一行
    val isFirstGroup = position < spanCount && exceptSpanIndex == spanIndex
    var isLastGroup = false
    if (size - position <= spanCount) {
        // position原属于最后一行
        val lastItemView = manager.findViewByPosition(size - 1)
        if (lastItemView != null) {
            val lastLp = lastItemView.layoutParams
            if (lastLp is StaggeredGridLayoutManager.LayoutParams) {
                // 列表最后一个item和当前item的spanIndex差等于position之差说明它们之间没有跨列的情况,当前item属于最后一行
                if (lastLp.spanIndex - spanIndex == size - 1 - position) {
                    isLastGroup = true
                }
            }
        }
    }
}

接下来就很简单了,设置主轴上的间隔

if (isFirstGroup) {
    // 是第一行
    if (isVertical) {
        outRect.top = mainPadding
    } else {
        outRect.left = mainPadding
    }
} else if (isLastGroup) {
    // 是最后一行要加边缘
    if (isVertical) {
        outRect.top = mainWidth
        outRect.bottom = mainPadding
    } else {
        outRect.left = mainWidth
        outRect.right = mainPadding
    }
} else {
    if (isVertical) {
        outRect.top = mainWidth
    } else {
        outRect.left = mainWidth
    }
}

交叉轴的间隔

交叉轴的分隔线最简单的是LinearLayoutManager,由于不存在多列直接设置为边缘间隔就可以了

if (isVertical) {
    outRect.left = crossPadding
    outRect.right = crossPadding
} else {
    outRect.top = crossPadding
    outRect.bottom = crossPadding
}

GridLayoutManager和StaggeredGridLayoutManager的交叉轴分隔线计算方法是一样的,可以统一处理,需要遵循的规则有两个

  1. 每列占用的左右间隔之和相等
  2. 每个item占用的右间隔和它相邻item占用的左间隔之和等于给定的间隔宽度

以下图为例,列表共4列,边缘间隔是15,item间隔是10,第二个item跨两列,每列应该占用的空间为15。

image.png

以第3个item为例,如何计算出它的左间隔(第三个绿色部分的宽度)和右间隔(第二个红色部分的宽度,公式如下

左间隔:到当前item的左边为止的总间隔(crossWidth * spanIndex + crossPadding)减去 到上一个item为止需要使用的总间隔(spanUsedWidth * spanIndex),这个例子中这两个值相等

同理右间隔:到当前item为止需要使用的总间隔(spanUsedWidth * (spanIndex + spanSize)) 减去 到当前item右边为止的总间隔(crossWidth * (spanIndex + spanSize - 1) + crossPadding);当然也可以用 当前item需要使用的总间隔( spanUsedWidth * spanSize) - 当前item已经使用的总间隔( crossWidth * (spanSize - 1) + lt)

这样通过归纳只使用两行代码就统合了所有情况

/**
 * 交叉轴间隔
 * [spanIndex] 当前item的以第几列开始
 * [spanSize] 当前item占用的列数
 */
private fun getItemCrossOffsets(outRect: Rect, isVertical: Boolean, spanCount: Int, spanIndex: Int, spanSize: Int) {
    // 每列占用的间隔
    val spanUsedWidth = (crossPadding * 2 + crossWidth * (spanCount - 1)) / spanCount
    // 到当前item的左边为止的总间隔 - 到上一个item为止需要使用的总间隔
    val lt = crossWidth * spanIndex + crossPadding - spanUsedWidth * spanIndex
    // 到当前item为止需要使用的总间隔 - 到当前item右边为止的总间隔
//        val rb = spanUsedWidth * (spanIndex + spanSize) - crossWidth * (spanIndex + spanSize - 1) - crossPadding
    // 当前item需要使用的总间隔 - 当前item已经使用的总间隔
    val rb = spanUsedWidth * spanSize - crossWidth * (spanSize - 1) - lt
    if (isVertical) {
        outRect.left = lt
        outRect.right = rb
    } else {
        outRect.top = lt
        outRect.bottom = rb
    }
}

作者:北野青阳
原文链接:https://juejin.cn/post/7248811984749527101

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述

欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值