You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LazyColumn跳转至新页面再返回后丢失滚动位置问题求助

LazyColumn跳转至新页面再返回后丢失滚动位置问题求助

各位大佬好,我最近碰到个挺棘手的问题,折腾了好久都没弄明白原因,想过来请教下:

我有一个MyView Composable,结构是Column套着一个LazyColumn,里面包含了几个头部行、内容行,还有几个横向滚动的LazyRow。但是当我用navController.navigate()跳转到下一页,再通过navController.popBackStack()返回时,这个LazyColumn的滚动状态总是会丢失,直接回到顶部。奇怪的是:

  • ViewModel并没有被重建
  • 填充那些LazyRow的Flow也没有发射新值
  • 而且LazyRow的横向滚动位置是正常保留的
  • 我在App其他地方用完全一样的导航逻辑,其他LazyColumn都能正常保留滚动位置,唯独这个不行

下面是我简化后的Composable代码:

@Composable
fun MyView(
    viewModel: MyViewModel,
    onNavigateToNext: (String) -> Unit,
) {
    val introItems by viewModel.introItems.collectAsStateWithLifecycle(emptyList())
    val row1items by viewModel.row1items.collectAsStateWithLifecycle(emptyList())
    val row2items by viewModel.row2items.collectAsStateWithLifecycle(emptyList())
    val row3items by viewModel.row3items.collectAsStateWithLifecycle(emptyList())
    val row4items by viewModel.row4items.collectAsStateWithLifecycle(emptyList())

    val listState = rememberLazyListState()

    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        LazyColumn(
            state = listState,
            contentPadding = PaddingValues(bottom = 8.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            modifier = Modifier
                .padding(horizontal = 8.dp)
                .windowInsetsPadding(WindowInsets.ime)
        ) {
            item(key = "header1") {
                Text("header1", modifier = Modifier.height(80.dp))
            }
            items(introItems, key = {it.itemID}) { listItem ->
                Text(listItem.name, modifier = Modifier.height(80.dp))
            }
            item(key = "header2") {
                Text("header2", modifier = Modifier.height(80.dp))
            }
            item(key = "lazyrow1") {
                LazyRow {
                    items(row1items) { listItem ->
                        Text(listItem.name, modifier = Modifier.height(150.dp))
                    }
                }
            }
            item(key = "header3") {
                Text("header3", modifier = Modifier.height(80.dp))
            }
            item(key = "lazyrow2") {
                LazyRow {
                    items(row2items) { listItem ->
                        Text(listItem.name, modifier = Modifier.height(150.dp))
                    }
                }
            }
            item(key = "header4") {
                Text("header4", modifier = Modifier.height(80.dp))
            }
            item(key = "lazyrow3") {
                LazyRow {
                    items(row3items) { listItem ->
                        Text(listItem.name, modifier = Modifier.height(150.dp))
                    }
                }
            }
            item(key = "header5") {
                Text("header5", modifier = Modifier.height(80.dp))
            }
            item(key = "lazyrow4") {
                LazyRow {
                    items(row4items) { listItem ->
                        Text(
                            listItem.name,
                            modifier = Modifier
                                .height(150.dp)
                                .clickable { onNavigateToNext(listItem.itemID.toString()) }
                        )
                    }
                }
            }
        }
    }
}

ViewModel里的Flow是这样实现的:

@OptIn(ExperimentalCoroutinesApi::class)
val row1Items: StateFlow<List<MyEntity>> = query
    .flatMapLatest { queryParams ->
        myDao.getItemsFlow(queryParams.query)
    }
    .map { list -> sortedItems(list) }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5.seconds),
        initialValue = emptyList(),
    )

大致逻辑是,当query这个数据类被修改时,会调用DAO的查询方法,然后对结果排序后发射到Flow中。

我自己排查了好久,实在找不到问题所在,想请教下各位有没有遇到过类似的情况,或者能给我一些排查方向?


可能的排查方向与解决方案

结合我自己的经验和对Compose状态的理解,给你几个可以尝试的方向:

  1. 用rememberSaveable强制保存LazyListState
    默认的rememberLazyListState()是用remember来保存状态的,但当Composable从导航栈中被临时移除时,remember的状态可能会被回收。换成rememberSaveable可以把状态持久化到Bundle中,确保返回时能恢复:
val listState = rememberSaveable(saver = LazyListState.Saver) {
    LazyListState()
}
  1. 检查所有Item的Key是否稳定唯一
    虽然你给introItems设置了itemID作为key,但那些LazyRow里的item没有指定key,这可能导致不必要的重组,间接影响父LazyColumn的状态。给LazyRow的item也加上稳定的key试试:
LazyRow {
    items(row1items, key = { it.itemID }) { listItem ->
        Text(listItem.name, modifier = Modifier.height(150.dp))
    }
}
  1. 避免不必要的Composable重组
    如果onNavigateToNext这个lambda每次重组时都是新的实例,可能会触发整个Composable的不必要重组,进而影响滚动状态。可以用remember把它缓存起来:
val onNavigateToNextRemembered = remember(onNavigateToNext) { onNavigateToNext }
// 然后在clickable里用这个缓存后的lambda
.clickable { onNavigateToNextRemembered(listItem.itemID.toString()) }
  1. 检查WindowInsets的影响
    你给LazyColumn加了windowInsetsPadding(WindowInsets.ime),导航返回时IME的Insets可能发生变化,导致LazyColumn的尺寸改变,从而重置滚动位置。可以先临时移除这个modifier,看看问题是否消失。如果是这个原因,可以用derivedStateOf来处理Insets的变化,避免直接影响滚动状态。

  2. 确认Flow的订阅状态
    虽然你说Flow没有发射新值,但SharingStarted.WhileSubscribed(5.seconds)会在页面离开5秒后停止订阅,返回时重新订阅。可以尝试把started参数改成SharingStarted.Lazily,看看是否能解决问题(不过这可能会增加资源消耗,只是用来排查)。

希望这些方法能帮你解决问题,要是有新的发现也欢迎回来分享~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:34:52