0

我需要一个 StaggeredGridLayoutManager 遵循这两个要求:

  • 根据每列设置的最小 dp 大小自动计算列数,列扩展以完全适合可用空间(例如:如果我定义 150dp 最小列,并且应用程序在 480dp 宽度的设备上运行,则网格视图将包含 3 列,剩余的 30dp 将被平均分割,这样每列都是 160dp 宽)
  • 网格布局必须在方向更改时重新计算跨度计数,因为在纵向和横向模式下适合屏幕的列数通常相距很远

到目前为止,我还没有需要交错网格项,所以我使用了 GridLayoutManager 的扩展来提供这个功能:

import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import kotlin.math.max
import kotlin.math.min

class GridAutoFitLayoutManager : GridLayoutManager {
    private var mColumnWidth = 0
    private var mMaximumColumns: Int
    private var mLastCalculatedWidth = -1

    @JvmOverloads
    constructor(
        context: Context?,
        columnWidthDp: Int,
        maxColumns: Int = 99
    ) : super(context, 1) //Initially set spanCount to 1, will be changed automatically later.
    {
        mMaximumColumns = maxColumns
        setColumnWidth(columnWidthDp)
    }

    private fun setColumnWidth(newColumnWidth: Int) {
        if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) {
            mColumnWidth = newColumnWidth
        }
    }

    override fun onLayoutChildren(
        recycler: Recycler,
        state: RecyclerView.State
    ) {
        val width = width
        val height = height
        if (width != mLastCalculatedWidth && mColumnWidth > 0 && width > 0 && height > 0) {
            val totalSpace: Int = if (orientation == RecyclerView.VERTICAL) {
                width - paddingRight - paddingLeft
            } else {
                height - paddingTop - paddingBottom
            }
            val spanCount = min(
                mMaximumColumns,
                max(1, totalSpace / mColumnWidth)
            )
            setSpanCount(spanCount)
            mLastCalculatedWidth = width
        }
        super.onLayoutChildren(recycler, state)
    }
}

在回收站视图中使用布局管理器:

recyclerView.layoutManager = GridAutoFitLayoutManager(context, resources.getDimension(R.dimen.grid_column_width).toInt())

最后,项目视图布局在宽度上设置为 match_parent,因此它们将占用布局管理器允许的尽可能多的空间。

但是,我无法让它与 StaggeredGridLayoutManager 一起使用。这是我的代码:

import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
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 mLastCalculatedWidth = -1

    @JvmOverloads
    constructor(
        context: Context?,
        columnWidthDp: Int,
        maxColumns: Int = 99
    ) : super(1, LinearLayout.VERTICAL) //Initially set spanCount to 1, will be changed automatically later.
    {
        mMaximumColumns = maxColumns
        setColumnWidth(columnWidthDp)
    }

    private fun setColumnWidth(newColumnWidth: Int) {
        if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) {
            mColumnWidth = newColumnWidth
        }
    }

    override fun onLayoutChildren(
        recycler: Recycler,
        state: RecyclerView.State
    ) {
        val width = width
        val height = height
        if (width != mLastCalculatedWidth && mColumnWidth > 0 && width > 0 && height > 0) {
            val totalSpace: Int = if (orientation == RecyclerView.VERTICAL) {
                width - paddingRight - paddingLeft
            } else {
                height - paddingTop - paddingBottom
            }
            val spanCount = min(
                mMaximumColumns,
                max(1, totalSpace / mColumnWidth)
            )
            setSpanCount(spanCount)
            mLastCalculatedWidth = width
        }
        super.onLayoutChildren(recycler, state)
    }
}

加载回收站视图后,我得到以下异常:

java.lang.IllegalStateException:当 RecyclerView 正在计算布局或滚动时无法调用此方法

问题是因为我在解决布局时调用了 setSpanCount(),这不是 GridLayoutManager 的问题,但在 StaggeredGridLayoutManager 中是不允许的。我的问题是,如果我不能在 onLayoutChildren() 期间设置跨度计数,我什么时候可以设置它

我知道我可以在初始化布局管理器之前计算出适合多少列,并将跨度计数传递给 StaggeredGridLayoutManager 构造函数。但是,这不会对方向更改做出反应,除非我向重新创建布局管理器的活动添加额外的逻辑,我希望避免这种情况。有没有办法让我的 GridAutoFitStaggeredLayoutManager 计算跨度计数自行处理方向更改?

4

1 回答 1

1

仔细研究了异常的原因,这应该可以防止错误:

import android.content.Context
import android.os.Handler
import android.util.DisplayMetrics
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlin.math.max
import kotlin.math.min

class GridAutoFitStaggeredLayoutManager : StaggeredGridLayoutManager {

    companion object {
        private fun setInitialSpanCount(context: Context?, columnWidthDp: Int) : Int {
            val displayMetrics: DisplayMetrics? = context?.resources?.displayMetrics
            return ((displayMetrics?.widthPixels ?: columnWidthDp) / columnWidthDp)
        }
    }

    private var mContext: Context?
    private var mColumnWidth = 0
    private var mMaximumColumns: Int
    private var mLastCalculatedWidth = -1

    @JvmOverloads
    constructor(
        context: Context?,
        columnWidthDp: Int,
        maxColumns: Int = 99
    ) : super(setInitialSpanCount(context, columnWidthDp), VERTICAL)
    {
        mContext = context
        mMaximumColumns = maxColumns
        mColumnWidth = columnWidthDp
    }

    override fun onLayoutChildren(
        recycler: Recycler,
        state: RecyclerView.State
    ) {
        if (width != mLastCalculatedWidth && width > 0) {
            recalculateSpanCount()
        }
        super.onLayoutChildren(recycler, state)
    }

    private fun recalculateSpanCount() {
        val totalSpace: Int = if (orientation == RecyclerView.VERTICAL) {
            width - paddingRight - paddingLeft
        } else {
            height - paddingTop - paddingBottom
        }
        val newSpanCount = min(
            mMaximumColumns,
            max(1, totalSpace / mColumnWidth)
        )
        queueSetSpanCountUpdate(newSpanCount)
        mLastCalculatedWidth = width
    }

    private fun queueSetSpanCountUpdate(newSpanCount: Int) {
        if(mContext != null) {
            Handler(mContext!!.mainLooper).post { spanCount = newSpanCount }
        }
    }
}
于 2020-06-29T14:23:40.010 回答