如何实现自适应列数的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 } } }
关键改动说明:
- 回调方法替换:放弃在
onLayoutChildren()中修改spanCount,改用onSizeChanged()——这是RecyclerView尺寸变化时的安全回调点。 - 避免重复计算:新增
mLastCalculatedSpanCount和mLastTotalSpace变量,只有当计算出的列数或可用空间和之前不同时,才更新spanCount,减少不必要的布局刷新。 - 修正列宽单位:构造方法中把传入的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
相关产品推荐
相关产品推荐

