英文:
Dynamically Change Span count and ModelView Size for Recycler View
问题
I have an epoxy recycler view that can display an item in the maximum possible size and also want to display the maximum number of items by adjusting span count and item size. For that, I am using this layout manager,
class GridAutoFitLayoutManager(
context: Context,
private val minItemSize: Int,
private val maxItemSize: Int
) : GridLayoutManager(context, 1) {
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
updateSpanCount()
super.onLayoutChildren(recycler, state)
}
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
val layoutParams = super.generateDefaultLayoutParams()
val itemSize = calculateItemSize()
layoutParams.width = itemSize
layoutParams.height = itemSize
return layoutParams
}
override fun generateLayoutParams(c: Context, attrs: android.util.AttributeSet): RecyclerView.LayoutParams {
val layoutParams = super.generateLayoutParams(c, attrs)
val itemSize = calculateItemSize()
layoutParams.width = itemSize
layoutParams.height = itemSize
return layoutParams
}
private fun updateSpanCount() {
val availableWidth = width - paddingRight - paddingLeft
val availableHeight = height - paddingTop - paddingBottom
val spanCount = calculateSpanCount(availableWidth, availableHeight)
if (spanCount == 0) {
setSpanCount(1)
} else {
setSpanCount(spanCount)
}
}
private fun calculateSpanCount(availableWidth: Int, availableHeight: Int): Int {
val itemCount = itemCount
val minSpanCount = 1 // Minimum span count
val maxSpanCount = (availableWidth / minItemSize).coerceAtMost(itemCount) // Maximum span count
var bestSpanCount = maxSpanCount
var bestItemSize = 0
val spacing = 20
for (spanCount in minSpanCount..maxSpanCount) {
val itemSize = calculateItemSizeWithSpan(spanCount)
if (itemSize in minItemSize..maxItemSize) {
val rowCount = availableHeight / itemSize
val availableWidthAfterSpacing = availableWidth - ((spanCount -1) * spacing)
val totalItemWidth = spanCount * itemSize
if (totalItemWidth <= availableWidthAfterSpacing) {
if (itemCount <= (rowCount * spanCount)) {
bestSpanCount = spanCount
bestItemSize = itemSize
break
}
}
}
}
return bestSpanCount
}
private fun calculateItemSizeWithSpan(spanCount: Int): Int {
val availableWidth = width - paddingRight - paddingLeft
val spacing = 20
val totalSpacing = spacing * (spanCount - 1)
val totalItemWidth = availableWidth - totalSpacing
return totalItemWidth / spanCount
}
private fun calculateItemSize(): Int {
val availableWidth = width - paddingRight - paddingLeft
val spanCount = spanCount
val spacing = 20
val totalSpacing = spacing * (spanCount - 1)
val totalItemWidth = availableWidth - totalSpacing
return totalItemWidth / spanCount
}
}
This is calculating the sizes correctly. However, I am facing an issue. In the first load, there are fewer items, say 8, so it displays an item of size 540 X 540.
Now when I switch to a different category, it has 40 items, so the best possible size is calculated as 240. In this case, the item is not drawn correctly.
Any idea why this is not drawn correctly? Is this a model view reuse issue?
英文:
I have en epoxy recycler view, that can display an item in the maximum possible size and also want to display the maximum number of items by adjusting span count and item size. For that, I am using this layout manager,
class GridAutoFitLayoutManager(
context: Context,
private val minItemSize: Int,
private val maxItemSize: Int
) : GridLayoutManager(context, 1) {
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
updateSpanCount()
super.onLayoutChildren(recycler, state)
}
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
val layoutParams = super.generateDefaultLayoutParams()
val itemSize = calculateItemSize()
layoutParams.width = itemSize
layoutParams.height = itemSize
return layoutParams
}
override fun generateLayoutParams(c: Context, attrs: android.util.AttributeSet): RecyclerView.LayoutParams {
val layoutParams = super.generateLayoutParams(c, attrs)
val itemSize = calculateItemSize()
layoutParams.width = itemSize
layoutParams.height = itemSize
return layoutParams
}
private fun updateSpanCount() {
val availableWidth = width - paddingRight - paddingLeft
val availableHeight = height - paddingTop - paddingBottom
val spanCount = calculateSpanCount(availableWidth, availableHeight)
if (spanCount == 0) {
setSpanCount(1)
} else {
setSpanCount(spanCount)
}
}
private fun calculateSpanCount(availableWidth: Int, availableHeight: Int): Int {
val itemCount = itemCount
val minSpanCount = 1 // Minimum span count
val maxSpanCount = (availableWidth / minItemSize).coerceAtMost(itemCount) // Maximum span count
var bestSpanCount = maxSpanCount
var bestItemSize = 0
val spacing = 20
for (spanCount in minSpanCount..maxSpanCount) {
val itemSize = calculateItemSizeWithSpan(spanCount)
if (itemSize in minItemSize..maxItemSize) {
val rowCount = availableHeight / itemSize
val availableWidthAfterSpacing = availableWidth - ((spanCount -1) * spacing)
val totalItemWidth = spanCount * itemSize
//&& (rowCount * spanCount) <= itemCount
if (totalItemWidth <= availableWidthAfterSpacing) {
if (itemCount <= (rowCount * spanCount)) {
bestSpanCount = spanCount
bestItemSize = itemSize
break
}
}
}
}
return bestSpanCount
}
private fun calculateItemSizeWithSpan(spanCount: Int): Int {
val availableWidth = width - paddingRight - paddingLeft
val spacing = 20
val totalSpacing = spacing * (spanCount - 1)
val totalItemWidth = availableWidth - totalSpacing
return totalItemWidth / spanCount
}
private fun calculateItemSize(): Int {
val availableWidth = width - paddingRight - paddingLeft
val spanCount = spanCount
val spacing = 20
val totalSpacing = spacing * (spanCount - 1)
val totalItemWidth = availableWidth - totalSpacing
return totalItemWidth / spanCount
}
}
This is calculating the sizes correctly. however, I am facing an issue. In the first load, there are fewer items say 8, so it displays an item of size 540 X 540.
Now when I switch to a different category, it is having 40 items, so the best possible size is calculated as 240. In this case, the item is not drawn correctly.
Any idea why this is not drawn correctly? Is this a moel view reuse issue ?
答案1
得分: 1
我建议添加一个 checkLayoutParams
方法的覆盖实现:
override fun checkLayoutParams(lp : RecyclerView.LayoutParams) : Boolean {
return lp.width == calculateItemSize()
}
这将在重用视图持有者的布局参数大小不合适时丢弃它们,并创建有效的参数来替换它们。
英文:
I suggest adding an override of checkLayoutParams
method:
override fun checkLayoutParams(lp : RecyclerView.LayoutParams) : Boolean {
return lp.width == calculateItemSize()
}
This will discard layout params of reused viewholders when their size is not okay and create valid params to replace them.
答案2
得分: 0
是这个一个模型视图重用的问题吗?
是的,epoxy模型将被重用,如果需要的话,你应该手动更改大小。
示例:
@ModelProp
fun setWidth(width: Int) {
view.updateLayoutParams {
this.width = width
}
}
但在你的情况下,我建议你可以查看:https://github.com/airbnb/epoxy/wiki/Grid-Support
然后根据每个特定情况进行覆盖。
英文:
> Is this a moel view reuse issue ?
Yes, the epoxy model will be reuse and you should manual change the size if you need.
Example:
@ModelProp
fun setWidth(width: Int) {
view.updateLayoutParams {
this.width = with
}
But in your case, I suggest you can check: https://github.com/airbnb/epoxy/wiki/Grid-Support
And do an override base on each specific case.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论