You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现自适应列数的StaggeredGridLayoutManager并适配横竖屏?

解决StaggeredGridLayoutManager动态计算列数及横竖屏适配问题

我完全懂你遇到的坑——StaggeredGridLayoutManager和GridLayoutManager的内部机制不一样,它不允许在布局计算的过程中(也就是onLayoutChildren()执行期间)修改spanCount,这就是为什么你会碰到那个讨厌的IllegalStateException。

别担心,我们换个思路就能解决:把列数计算的逻辑移到onSizeChanged()方法里。这个方法会在RecyclerView的尺寸发生变化(比如横竖屏切换、窗口大小调整)时被调用,而且此时布局计算的状态是安全的,不会触发异常。

下面是修改后的完整代码:

import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlin.math.max
import kotlin.math.min

class GridAutoFitStaggeredLayoutManager : StaggeredGridLayoutManager {
    private var mColumnWidth = 0
    private var mMaximumColumns: Int
    private var mLastCalculatedSpanCount = -1
    private var mLastTotalSpace = -1

    @JvmOverloads
    constructor(
        context: Context?, columnWidthDp: Int, maxColumns: Int = 99
    ) : super(1, LinearLayout.VERTICAL) {
        mMaximumColumns = maxColumns
        // 把传入的dp值转换为像素,避免计算错误
        mColumnWidth = context?.resources?.getDimensionPixelSize(columnWidthDp) ?: columnWidthDp
    }

    override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
        super.onSizeChanged(width, height, oldWidth, oldHeight)
        
        // 计算当前可用的总空间
        val totalSpace = if (orientation == VERTICAL) {
            width - paddingLeft - paddingRight
        } else {
            height - paddingTop - paddingBottom
        }

        // 避免无效计算
        if (totalSpace <= 0 || mColumnWidth <= 0) return

        // 计算合适的列数:不超过最大列数,且至少1列
        val calculatedSpanCount = min(
            mMaximumColumns,
            max(1, totalSpace / mColumnWidth)
        )

        // 只有当列数或可用空间变化时才更新,减少不必要的布局刷新
        if (calculatedSpanCount != mLastCalculatedSpanCount || totalSpace != mLastTotalSpace) {
            setSpanCount(calculatedSpanCount)
            mLastCalculatedSpanCount = calculatedSpanCount
            mLastTotalSpace = totalSpace
        }
    }
}

关键改动说明:

  1. 回调方法替换:放弃在onLayoutChildren()中修改spanCount,改用onSizeChanged()——这是RecyclerView尺寸变化时的安全回调点。
  2. 避免重复计算:新增mLastCalculatedSpanCount和mLastTotalSpace变量,只有当计算出的列数或可用空间和之前不同时,才更新spanCount,减少不必要的布局刷新。
  3. 修正列宽单位:构造方法中把传入的dp值转换为像素,避免用dp直接进行像素空间计算导致的适配错误。

使用方式:

和你之前用GridAutoFitLayoutManager的方式完全一样,直接设置给RecyclerView即可,横竖屏切换时会自动重新计算列数:

// 假设你的dimens.xml里定义了grid_column_width为150dp
recyclerView.layoutManager = GridAutoFitStaggeredLayoutManager(context, R.dimen.grid_column_width)

额外注意:

  • 确保你的Item布局宽度设置为match_parent,这样才能填满StaggeredGridLayoutManager分配的列宽。
  • 如果RecyclerView的padding有动态变化,onSizeChanged()也会被触发,列数会自动重新计算适配新空间。

内容的提问来源于stack exchange,提问作者PM4

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 11:12:38