RecyclerView使用GridLayoutManager时,自定义ItemDecoration平分间隔问题

2,057 阅读2分钟

经常写android的朋友都知道,在使用recyclerView实现网格布局效果时,只要传入GridLayoutManager就可以实现。但是!想要使用ItemDecoration设置分隔线的时候却可能会出现问题。

(太长不看的朋友可以直接跳到最终代码)

问题出现

首先实现一个recyclerView的网格列表↓(画面是演示页面)

f2604d91abad4e01868def6c7198a79.png

按照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)))
    //...
}

实际效果也很感人地出问题了↓

7a84d17066d479c9b13b28aaf868191.png

问题分析

经过调查了解 啊 GridLayoutManager在横向上 会把宽度平均分给每个item,因此,列表内的分配空间状况应为↓

9eae4475f78f15f934b95a2b4c6f922.png

这样一来,解决方案就清楚了。



也麻烦了😡

解决方案

这里是分析计算方法的过程,太长不看的朋友也可以直接跳到最终代码

首先看题干,我的目标是在五项间插入四个分隔距离,设距离为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
依次计算,可得:

itemactual-Lactual-R
00target*(n-1)/n
1target*1/ntarget*(n-2)/n
2target*2/ntarget*(n-3)/n
3target*3/ntarget*(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")
        }
    }
}