GridSpaceItemDecoration 完整适配版(跨列标题+均分网格+首行顶部间距)

该工具类为 RecyclerView GridLayoutManager 专用网格间距装饰器

解决市面上绝大多数 GridDecoration 的通病:

  1. 存在跨列整行Item(标题/分组)时,position%spanCount 列数计算错乱
  2. 首尾Item左右边距不均、视觉错位
  3. 行间距叠加导致双倍高度
  4. 无法单独控制「网格第一行顶部间距」(标题和网格间距不协调)
  5. 边界越界崩溃问题
特性:完全兼容多类型网格、跨列标题、自动识别真实行列、均分空白、无叠加间距、可配置首行顶部边距
完整代码如下
package com.xxx.core.utils

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

/**
 * 【终极适配版】RecyclerView 网格间距装饰器
 * 适配:普通3列网格 + 跨列整行标题混合布局(工作台常用)
 *
 * 核心优势(解决所有旧版BUG)
 * 1. 不使用 position % spanCount 计算列数,使用系统原生 [spanLookup.getSpanIndex] 获取真实列下标
 *    彻底解决「存在跨列Item后列数错乱、间距不均」问题
 * 2. 通过 spanGroupIndex 识别真实行,自动判断当前Item是否为「本行最后一个」
 *    解决最后一列右边距残留问题
 * 3. 行间距只使用 bottom 赋值,top默认0,杜绝上下叠加双倍间距
 * 4. 单独配置网格首行 top 间距,完美适配【标题+网格】UI结构
 * 5. 做了 position+1 越界保护,不会数组越界崩溃
 * 6. 兼容 includeEdge 内外边距模式,新旧项目通用
 *
 * @param spanCount 网格总列数(例:3列传3)
 * @param space 网格Item之间的间距(单位px,建议dp转px传入)
 * @param includeEdge 是否包含屏幕左右边缘间距
 *        true:整列表左右留白,所有Item均分间隙
 *        false:每行第一个Item left=0、最后一个Item right=0,贴合边缘
 * @param firstRowTopSpace 网格第一行顶部间距(仅普通网格Item生效,标题不生效)默认 0
 *        用于解决「标题下方紧贴网格,没有间距」的UI问题
 */
class GridSpaceItemDecoration(
    private val spanCount: Int,
    private val space: Int,
    private val includeEdge: Boolean,
    private val firstRowTopSpace: Int = 0
) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val position = parent.getChildAdapterPosition(view)
        val itemCount = parent.adapter?.itemCount ?: 0
        val manager = parent.layoutManager as GridLayoutManager
        val spanLookup = manager.spanSizeLookup

        // 当前item占用的列跨度
        val itemSpan = spanLookup.getSpanSize(position)

        // ====================== 跨列整行Item(分组标题/通栏布局)======================
        // 整行占满所有列的View(标题)统一上下均分间距,不参与网格列均分逻辑
        if (itemSpan == spanCount) {
            outRect.set(0, space / 2, 0, space / 2)
            return
        }

        // ====================== 普通网格Item逻辑 ======================
        // 获取当前Item在本行中的真实列下标(系统原生API,不受标题跨列影响)
        val realColumn = spanLookup.getSpanIndex(position, spanCount)
        // 获取当前Item所在的行索引(网格真实行号)
        val currentGroup = spanLookup.getSpanGroupIndex(position, spanCount)

        if (includeEdge) {
            // 模式1:包含边缘,屏幕左右留有空白,所有Item均匀分摊间距
            outRect.left = space - realColumn * space / spanCount
            outRect.right = (realColumn + 1) * space / spanCount
        } else {
            // 模式2:不包含边缘
            // 本行第一个Item:左边距0
            outRect.left = if (realColumn == 0) 0 else space / 2

            // 判断当前Item是否是【本行最后一个】
            // 原理:下一个Item行号 != 当前行号 = 当前是行尾
            val isRowLast: Boolean = if (position + 1 < itemCount) {
                val nextGroup = spanLookup.getSpanGroupIndex(position + 1, spanCount)
                nextGroup != currentGroup
            } else {
                // 最后一条数据,直接判定行尾
                true
            }

            // 本行最后一个Item:右边距0
            outRect.right = if (isRowLast) 0 else space / 2
        }

        // 行间距:只留bottom,top默认0,杜绝双倍间距叠加
        // 单独适配:网格第一行可自定义顶部间距(适配标题下方留白)
        outRect.top = if (currentGroup == 0) firstRowTopSpace else 0
        outRect.bottom = space
    }
}

一、适配场景

  1. 纯网格列表(2列3列/4列均分)
  2. 标题通栏 + 网格Item混合布局(最常用工作台布局)
  3. 需要首行单独顶部留白的UI
  4. 禁止行间距双倍叠加的精致UI

二、调用示例(工作台标准用法)

// 3列网格、间距12dp、无边缘留白、首行顶部额外12dp间距
binding.rvWorkGrid.addItemDecoration(
    GridSpaceItemDecoration(
        spanCount = 3,
        space = 12.dp2px(requireContext()),
        includeEdge = false,
        firstRowTopSpace = 12.dp2px(requireContext())
    )
)

三、效果说明

includeEdge = false(推荐UI标准)
  • 每行第一个Item:left=0
  • 每行最后一个Item:right=0
  • 中间Item:左右均分间隙
  • Item之间间距统一、无重叠、无错位
  • 标题通栏单独上下留白
  • 网格第一行可独立设置 top 间距,完美分隔标题与内容
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容