使用Jetpack Compose Navigation导航后保留列表状态的最佳实践
导航返回后LazyColumn滚动状态保留实现方案
核心问题原因
你当前使用rememberLazyListState()创建列表状态,其内部基于remember实现,状态生命周期和列表页可组合项的组合周期绑定。导航到详情页时列表页会退出组合树,remember存储的状态被销毁,返回列表页时会重新初始化listState,所以滚动位置会重置到顶部。
优先实现方案(符合编码规范)
普通场景下优先使用rememberSaveable方案,该方案符合单一职责原则,UI相关状态存放在UI层,无需污染业务ViewModel,代码量极少。
LazyListState官方已经内置了序列化Saver,无需自己编写状态保存逻辑,直接修改列表状态初始化代码即可:
// 把原来的 val listState = rememberLazyListState() 替换为以下代码 val listState = rememberSaveable(saver = LazyListState.Saver) { LazyListState() }
rememberSaveable的状态存储在组合树之外的SavedStateRegistry中,只要列表页还在导航回退栈中,状态就不会丢失,返回列表页时会自动恢复滚动位置,无需额外编写滚动逻辑。
备选方案:ViewModel存储(特殊场景使用)
如果你的需求需要支持APP被系统杀死重启后仍保留滚动位置,或者滚动位置需要和业务逻辑联动(比如上报用户浏览位置),可以使用ViewModel + SavedStateHandle的方案:
1. ViewModel层实现
class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // 通过SavedStateHandle存储,支持进程杀死后恢复 private val _firstVisibleIndex = savedStateHandle.getStateFlow("list_first_index", 0) private val _firstVisibleOffset = savedStateHandle.getStateFlow("list_first_offset", 0) fun saveScrollState(index: Int, offset: Int) { savedStateHandle["list_first_index"] = index savedStateHandle["list_first_offset"] = offset } fun getScrollState(): Pair<Int, Int> { return _firstVisibleIndex.value to _firstVisibleOffset.value } }
2. 列表页实现
val (initIndex, initOffset) = viewModel.getScrollState() val listState = rememberLazyListState( initialFirstVisibleItemIndex = initIndex, initialFirstVisibleItemScrollOffset = initOffset ) // 监听滚动变化实时保存到ViewModel LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }.collect { (index, offset) -> viewModel.saveScrollState(index, offset) } }
选型建议
| 适用场景 | 方案选择 |
|---|---|
| 仅需要导航返回保留滚动位置 | 优先rememberSaveable方案 |
| 需要跨进程重启保留滚动位置、滚动位置关联业务逻辑 | 选择ViewModel+SavedStateHandle方案 |
内容的提问来源于stack exchange,提问作者A.D
相关产品推荐
相关产品推荐

