I'm using a 'LazyVerticalGrid' with 2 cells to show list items in a compose project. What I want is leting the last item occupy 2 cells, like this:
My approach is set the last item's 'span' param to 'GridItemSpan(2)', but when items are not exceeding one page, it is normal.However, when the items exceeds one page, I slide the list, it will crash.
//My code:
val list: List<Int> = ArrayList<Int>().apply {
repeat(1000) {
add(it)
}
}
LazyVerticalGrid(
cells = GridCells.Fixed(2),
state = state
) {
items(list) {
Text(text = "$it")
}
// both GridItemSpan(2) or GridItemSpan(1) here will crashed
item(span = {
GridItemSpan(2)
}) {
Text(text = "End")
}
}
// The Error Log:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at java.util.ArrayList.get(ArrayList.java:437)
at androidx.compose.foundation.lazy.grid.LazyGridSpanLayoutProvider.getLineConfiguration(LazyGridSpanLayoutProvider.kt:96)
at androidx.compose.foundation.lazy.grid.LazyMeasuredLineProvider.getAndMeasure-bKFJvoY(LazyMeasuredLineProvider.kt:60)
at androidx.compose.foundation.lazy.grid.LazyGridMeasureKt.measureLazyGrid-0cYbdkg(LazyGridMeasure.kt:140)
at androidx.compose.foundation.lazy.grid.LazyGridKt$rememberLazyGridMeasurePolicy$1$1.measure-3p2s80s(LazyGrid.kt:324)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke-0kLqBqw(LazyLayout.kt:68)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke(LazyLayout.kt:61)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:514)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:54)
at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:96)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:306)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:40)
at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$2.invoke(OuterMeasurablePlaceable.kt:99)
at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$2.invoke(OuterMeasurablePlaceable.kt:98)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:1918)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:121)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:88)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:76)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:98)
at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1289)
at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release$default(LayoutNode.kt:1285)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:170)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:246)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:38)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:191)
at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:693)
at androidx.compose.ui.node.Owner$DefaultImpls.measureAndLayout$default(Owner.kt:191)
at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:882)
at android.view.View.draw(View.java:23019)
at android.view.View.updateDisplayListIfDirty(View.java:21883)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499)
at android.view.View.updateDisplayListIfDirty(View.java:21836)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499)
at android.view.View.updateDisplayListIfDirty(View.java:21836)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499)
// code where crashed:
// code in LazyGridSpanLayoutProvider.kt file
fun getLineConfiguration(lineIndex: Int): LineConfiguration {
if (!itemsProvider.hasCustomSpans) {
// Quick return when all spans are 1x1 - in this case we can easily calculate positions.
val firstItemIndex = lineIndex * slotsPerLine
return LineConfiguration(
firstItemIndex,
getDefaultSpans(slotsPerLine.coerceAtMost(totalSize - firstItemIndex)
.coerceAtLeast(0))
)
}
val bucket = lineIndex / bucketSize
// We can calculate the items on the line from the closest cached bucket start item.
var currentLine = min(bucket, bucketStartItemIndex.size - 1) * bucketSize
var currentItemIndex = bucketStartItemIndex[min(bucket, bucketStartItemIndex.size - 1)]
/** Here crashed!!!
var knownCurrentItemSpan = bucketStartKnownSpan[min(bucket, bucketStartItemIndex.size - 1)]
**/
// ... but try using the more localised cached values.
if (lastLineIndex in currentLine..lineIndex) {
// The last calculated value is a better start point. Common when scrolling main axis.
currentLine = lastLineIndex
currentItemIndex = lastLineStartItemIndex
knownCurrentItemSpan = lastLineStartKnownSpan
} else if (bucket == cachedBucketIndex && lineIndex - currentLine < cachedBucket.size) {
// It happens that the needed line start is fully cached. Common when scrolling in
// reverse main axis, as we decided to cacheThisBucket previously.
currentItemIndex = cachedBucket[lineIndex - currentLine]
currentLine = lineIndex
knownCurrentItemSpan = 0
}
val cacheThisBucket = currentLine % bucketSize == 0 &&
lineIndex - currentLine in 2 until bucketSize
if (cacheThisBucket) {
cachedBucketIndex = bucket
cachedBucket.clear()
}
check(currentLine <= lineIndex)
while (currentLine < lineIndex && currentItemIndex < totalSize) {
if (cacheThisBucket) {
cachedBucket.add(currentItemIndex)
}
var spansUsed = 0
while (spansUsed < slotsPerLine && currentItemIndex < totalSize) {
val span = if (knownCurrentItemSpan == 0) {
spanOf(currentItemIndex, slotsPerLine - spansUsed)
} else {
knownCurrentItemSpan.also { knownCurrentItemSpan = 0 }
}
if (spansUsed + span > slotsPerLine) {
knownCurrentItemSpan = span
break
}
currentItemIndex++
spansUsed += span
}
++currentLine
if (currentLine % bucketSize == 0 && currentItemIndex < totalSize) {
val currentLineBucket = currentLine / bucketSize
// This should happen, as otherwise this should have been used as starting point.
check(bucketStartItemIndex.size == currentLineBucket)
bucketStartItemIndex.add(currentItemIndex)
bucketStartKnownSpan.add(knownCurrentItemSpan)
}
}
lastLineIndex = lineIndex
lastLineStartItemIndex = currentItemIndex
lastLineStartKnownSpan = knownCurrentItemSpan
val firstItemIndex = currentItemIndex
val spans = mutableListOf<GridItemSpan>()
var spansUsed = 0
while (spansUsed < slotsPerLine && currentItemIndex < totalSize) {
val span = if (knownCurrentItemSpan == 0) {
spanOf(currentItemIndex, slotsPerLine - spansUsed)
} else {
knownCurrentItemSpan.also { knownCurrentItemSpan = 0 }
}
if (spansUsed + span > slotsPerLine) break
currentItemIndex++
spans.add(GridItemSpan(span))
spansUsed += span
}
return LineConfiguration(firstItemIndex, spans)
}
Here is a copy of the official sample: LazyVerticalGrid
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.GridItemSpan
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
val sections = (0 until 25).toList().chunked(5)
LazyVerticalGrid(
cells = GridCells.Fixed(3),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
sections.forEachIndexed { index, items ->
item(span = { GridItemSpan(maxLineSpan) }) {
Text(
"This is section $index",
Modifier.border(1.dp, Color.Gray).height(80.dp).wrapContentSize()
)
}
items(
items,
// not required as it is the default
span = { GridItemSpan(1) }
) {
Text(
"Item $it",
Modifier.border(1.dp, Color.Blue).height(80.dp).wrapContentSize()
)
}
}
}
It work well, but when I increase the list "sections" ,
from:
val sections = (0 until 25).toList().chunked(5)
to:
val sections = (0 until 200).toList().chunked(5)
It crashed like my code.