Compose LazyVerticalGrid切换跨度后滚动崩溃:IllegalStateException异常
解决Compose LazyVerticalGrid切换Item跨度后滚动崩溃问题
问题现象
在最新版Compose中,点击按钮切换列表项的跨度(从2改为1)后滚动应用,会触发如下崩溃:
FATAL EXCEPTION: main Process: com.example.myapplication, PID: 17202 java.lang.IllegalStateException: Place was called on a node which was placed already at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.kt:941) at androidx.compose.ui.node.InnerNodeCoordinator.placeAt-f8xVGno(InnerNodeCoordinator.kt:137) at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.kt:35) at androidx.compose.ui.layout.Placeable$PlacementScope.placeWithLayer-aW-9-wM(Placeable.kt:471) ...
复现代码
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable fun HomeScreen() { var spanNumber by remember { mutableStateOf(false) } val list = listOf(1,2,3,4,5,6,7,8,9,10) LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(8.dp) ) { item(span = { GridItemSpan(2) }) { Button(onClick = { spanNumber = !spanNumber}) { Text(text = "Change mode") } } repeat(list.size) { item(span = { GridItemSpan(currentLineSpan = if (spanNumber) { 2 } else { 1 } ) }) { Column(verticalArrangement = Arrangement.Center) { Text(text = "Hello $it", modifier = Modifier .height(200.dp) .padding(horizontal = 4.dp)) } } } } }
问题原因
动态修改LazyVerticalGrid中item的跨度时,Compose的LazyGrid复用机制会出现状态冲突:已存在的布局节点在跨度变化后,滚动触发视图复用时被重复调用place方法,导致节点状态异常,触发IllegalStateException。
解决方案
方案1:为动态跨度的Item添加唯一Key
给每个动态跨度的item设置唯一key,让Compose能够准确识别item的变化,避免复用错误的布局节点。修改后的代码如下:
@Composable fun HomeScreen() { var spanNumber by remember { mutableStateOf(false) } val list = listOf(1,2,3,4,5,6,7,8,9,10) LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(8.dp) ) { item(span = { GridItemSpan(2) }) { Button(onClick = { spanNumber = !spanNumber}) { Text(text = "Change mode") } } repeat(list.size) { index -> // 添加唯一key,确保Compose正确识别item item(key = index, span = { GridItemSpan(currentLineSpan = if (spanNumber) 2 else 1) }) { Column(verticalArrangement = Arrangement.Center) { Text(text = "Hello $index", modifier = Modifier .height(200.dp) .padding(horizontal = 4.dp)) } } } } }
方案2:为LazyVerticalGrid添加依赖状态的Key
当跨度变化时,强制重建整个LazyVerticalGrid,避免节点复用问题。适合item数量较少的场景:
LazyVerticalGrid( // 当spanNumber变化时,重建整个Grid key = spanNumber, columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(8.dp) ) { // ... 原有内容 }
方案对比
- 方案1更高效:仅更新状态变化的item,性能损耗小,适合大量item的场景。
- 方案2实现简单:直接重建Grid,无需逐个处理item,但会有短暂的重建开销,适合小体量列表。
内容的提问来源于stack exchange,提问作者Hazard Space
相关产品推荐
相关产品推荐

