GridLayoutManager增加间隙很奇怪,假如有三列,第一列左边距0dp,第三列右边距0dp,列之间的间隙为12dp,如果简单的通过设置第一列left=0dp,right=6dp,第二列left=6dp,right=6dp,第三列left=6dp,right=0dp,实现的效果并不能满足实际需求。这是因为6dp 是固定值,但 RecyclerView 的可用宽度(屏幕宽度 - padding)是动态的。如果总列宽和间隙的累加值与屏幕宽度不匹配,会导致:
- 间隙过大:列宽被挤压,内容显示不全。
- 间隙过小:右侧出现空白,无法对齐边缘。
所以通过 比例分配 或 公式化动态计算,让总间隙和列宽自动适配屏幕宽度:
import android.annotation.SuppressLint
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import xxx.common.kts.dp2pxInt
import kotlin.math.roundToInt
/**
* GridLayoutManager 间距 ItemDecoration(dp 入参,无跨列)
*
* - 左右边缘恒定为 0:最左 item 的 left=0,最右 item 的 right=0
* (如果需要边缘留白,请在 RecyclerView 的 paddingStart/paddingEnd 或父布局中控制)
*
* - 条目之间横向间距恒定为 itemHSpaceDp(经典算法 includeEdge=false)
* - 每个条目都设置 top/bottom(不做首行/末行特殊处理)
* @param itemHSpaceDp 传dp值
* @param topDp 传dp值
* @param bottomDp 传dp值
*/
class CommonGridItemDecoration(
private val itemHSpaceDp: Float, //条目之间横向间距(dp)
private val topDp: Float, //每个条目顶部间距(dp)
private val bottomDp: Float, //每个条目底部间距(dp)
) : RecyclerView.ItemDecoration() {
private var itemHPx = Int.MIN_VALUE
private var topPx = Int.MIN_VALUE
private var bottomPx = Int.MIN_VALUE
@SuppressLint("WrongConstant")
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val lm = parent.layoutManager as? GridLayoutManager ?: return
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
// dp -> px:只算一次并缓存
if (itemHPx == Int.MIN_VALUE) {
itemHPx = itemHSpaceDp.dp2pxInt
topPx = topDp.dp2pxInt
bottomPx = bottomDp.dp2pxInt
}
val spanCount = lm.spanCount.coerceAtLeast(1)
val column = position % spanCount
outRect.top = topPx
outRect.bottom = bottomPx
// 经典算法(includeEdge = false):
// left = column * S / spanCount
// right = S - (column + 1) * S / spanCount
// 性质:
// - column=0 => left=0
// - column=last => right=0
// - 相邻两列 gap:right(c) + left(c+1) = S
val s = itemHPx.toFloat()
val n = spanCount.toFloat()
outRect.left = (column * s / n).roundToInt()
outRect.right = (s - (column + 1) * s / n).roundToInt()
}
}
也可以封装最左/右侧带参数的,但是那样会让整个算法比较复杂,不利于维护,所以这里没有写出来,如果需要可以让AI帮忙封装。