经常写android的朋友都知道,在使用recyclerView实现网格布局效果时,只要传入GridLayoutManager就可以实现。但是!想要使用ItemDecoration设置分隔线的时候却可能会出现问题。
(太长不看的朋友可以直接跳到最终代码)
问题出现
首先实现一个recyclerView的网格列表↓(画面是演示页面)
按照LinearLayoutManager添加分隔线时的方式,想当然的,编写了如下代码进行实现↓
class GridItemDecoration(horizontal: Int, vertical: Int): ItemDecoration() {
private val spaceHorizontal by lazy { horizontal }
private val spaceVertical by lazy { vertical }
private var spanCount = -1
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
if (parent.layoutManager is GridLayoutManager) {
if (spanCount == -1) {
spanCount =
(parent.layoutManager as GridLayoutManager).spanCount
}
val position = parent.getChildAdapterPosition(view)
if (position != 0) { /* 排除第一项 */
if (position < spanCount) { /* 第一行其余项 */
outRect.left = spaceHorizontal
} else if (position % spanCount == 0) { /* 其余行第一项(左起第一列) */
outRect.top = spaceVertical
} else { /* 剩余的项 */
outRect.top = spaceVertical
outRect.left = spaceHorizontal
}
}
} else {
Log.e("GridItemDecoration", "layoutManager isn't GridLayoutManager")
}
}
}
嗯,完美。然后直接进行一个调用
rvDisplay.apply {
//...
addItemDecoration(GridItemDecoration(15.dp(mThis), 10.dp(mThis)))
//...
}
实际效果也很感人地出问题了↓
问题分析
经过调查了解 啊 GridLayoutManager在横向上 会把宽度平均分给每个item,因此,列表内的分配空间状况应为↓
这样一来,解决方案就清楚了。
。
。
。
也麻烦了😡
解决方案
这里是分析计算方法的过程,太长不看的朋友也可以直接跳到最终代码
首先看题干,我的目标是在五项间插入四个分隔距离,设距离为target
而实际上需要在被评分的五项的容器内,让第2、3、4项的左右各空出不同的宽度,才能实现想要的效果
。为了保持容器宽度,每一项需要 空出的是总计宽度相同的空间 ,设这个空间为actual,
由此可得target*4 = actual*5
把五项换成任意n项,也就是target*(n-1) = actual*n
因此,第一项右侧就是target*(n-1)/n
第二项左侧actual-2L就应该是target-(target*(n-1)/n)=target*1/n,右侧actual-2R则是actual-target*1/n=target*(n-2)/n
依次计算,可得:
| item | actual-L | actual-R |
|---|---|---|
| 0 | 0 | target*(n-1)/n |
| 1 | target*1/n | target*(n-2)/n |
| 2 | target*2/n | target*(n-3)/n |
| 3 | target*3/n | target*(n-4)/n |
...
好吧,我数学知识大部分还给老师了所以是用总结的方式得出公式的😡。在单行内,第i项的左边距为target*i/n,右边距为target*(n-(i+1))/n
至此,问题可告解决。
最终代码
class GridItemDecoration(horizontal: Int, vertical: Int): ItemDecoration() {
private val spaceHorizontal by lazy { horizontal }
private val spaceVertical by lazy { vertical }
private var spanCount = -1
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
if (parent.layoutManager is GridLayoutManager) {
if (spanCount == -1) {
spanCount =
(parent.layoutManager as GridLayoutManager).spanCount
}
val position = parent.getChildAdapterPosition(view)
/* 绘制垂直边距 */
if (position >= spanCount) { //第一行之外绘制垂直边距
outRect.top = spaceVertical
}
/* 绘制水平边距 */
val pInLine: Int = position % spanCount // 行内位置
if (pInLine == 0) {
outRect.left = 0
} else {
outRect.left = (pInLine * 1f * spaceHorizontal / spanCount).toInt()
}
outRect.right = ((spanCount - pInLine - 1) * 1f * spaceHorizontal / spanCount).toInt()
} else {
Log.e("GridItemDecoration", "layoutManager isn't GridLayoutManager")
}
}
}