前言
2023已过半,才发现我已经大半年没写博客了,痛定思痛决定水一篇。
不知道大家平时干活的时候有没有被RecyclerView列表的分隔线困扰过,app里一般都会有各式各样的列表,横的竖的、网格、瀑布流款式的,每次要给列表设分隔线时都要自己写一个特化的ItemDecoration,既费事又难以复用,那能不能写一个适用大多数场景的ItemDecoration来减轻这类负担呢?
别急,本篇文章就给大家带来一个我自用的通用ItemDecoration,支撑linear grid staggered LayoutManager,支撑横竖向、跨列等状况;支撑边际、横纵向分隔线不同宽度,运用也十分简略。
效果图和代码
代码
单个类能够直接运用,库房包括demo
效果图 网格款式
瀑布流
这个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来判别,groupIndex==0在首行,groupIndex==lastGroupIndex在最终一行
// 当时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的穿插轴分隔线计算方法是相同的,能够统一处理,需求遵循的规则有两个
- 每列占用的左右距离之和持平
- 每个item占用的右距离和它相邻item占用的左距离之和等于给定的距离宽度
以下图为例,列表共4列,边际距离是15,item距离是10,第二个item跨两列,每列应该占用的空间为15。
以第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
}
}