具有自动适应跨度计数的 StaggeredGridLayoutManager
StaggeredGridLayoutManager with auto-fit span count
-
android-gridview
-
android-gridlayout
-
gridlayoutmanager
-
android-recyclerview
-
staggeredgridlayoutmanager
我需要一个符合以下两个要求的 StaggeredGridLayoutManager:
- 根据每列设置的最小 dp 大小自动计算列数,列扩展以完全适合可用 space(例如:如果我定义 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,因此它们将占据布局管理器允许的 space 大小。
但是,我无法让它与 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: Cannot call this method while RecyclerView is computing a layout or scrolling
问题是因为我在解析布局时调用了 setSpanCount(),这不是 GridLayoutManager 的问题,但在 StaggeredGridLayoutManager 中是不允许的。我的问题是,如果我不能在 onLayoutChildren() 期间设置跨度计数,我什么时候可以设置它?
我知道我可以在初始化布局管理器之前计算适合的列数,并将跨度计数传递给 StaggeredGridLayoutManager 构造函数。但是,这不会对方向变化做出反应,除非我向 activity 添加额外的逻辑来重新创建布局管理器,我希望避免这种情况。有没有办法让我的 GridAutoFitStaggeredLayoutManager 计算跨度计数 和 自行处理方向变化?
仔细查看了异常的原因,这应该可以防止错误:
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 }
}
}
}
android-gridview
android-gridlayout
gridlayoutmanager
android-recyclerview
staggeredgridlayoutmanager
我需要一个符合以下两个要求的 StaggeredGridLayoutManager:
- 根据每列设置的最小 dp 大小自动计算列数,列扩展以完全适合可用 space(例如:如果我定义 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,因此它们将占据布局管理器允许的 space 大小。
但是,我无法让它与 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: Cannot call this method while RecyclerView is computing a layout or scrolling
问题是因为我在解析布局时调用了 setSpanCount(),这不是 GridLayoutManager 的问题,但在 StaggeredGridLayoutManager 中是不允许的。我的问题是,如果我不能在 onLayoutChildren() 期间设置跨度计数,我什么时候可以设置它?
我知道我可以在初始化布局管理器之前计算适合的列数,并将跨度计数传递给 StaggeredGridLayoutManager 构造函数。但是,这不会对方向变化做出反应,除非我向 activity 添加额外的逻辑来重新创建布局管理器,我希望避免这种情况。有没有办法让我的 GridAutoFitStaggeredLayoutManager 计算跨度计数 和 自行处理方向变化?
仔细查看了异常的原因,这应该可以防止错误:
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 }
}
}
}