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 ?
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.