Jetpack Compose列表重排后重置滚动位置的最优方案咨询
问题根源
- 你给
LaunchedEffect传入的key是coroutineScope,这个值是rememberCoroutineScope()返回的,在整个Composable生命周期内不会发生变更,所以LaunchedEffect只会在第一次组合时执行一次,后续scrollToTop状态更新也不会触发重新执行。 - 没有对
scrollToTop状态做复位处理,即使第一次滚动完成后,状态一直是true,后续下发相同的true值也不会触发状态变化回调。
最优实现方案
1. 修改ViewModel层逻辑
增加滚动完成后的复位方法,保证每次重排事件下发后scrollToTop都是从false变为true,触发状态变化:
class ViewItemsState(val items: List<ViewItem>, val scrollToTop: Boolean) class ExampleViewModel() : ViewModel() { var itemsLiveData = MutableLiveData<ViewItemsState>() // 重排完成后调用这个方法下发新状态 fun onItemsReordered(newItems: List<ViewItem>) { itemsLiveData.value = ViewItemsState(newItems, scrollToTop = true) } // 滚动完成后调用复位滚动标记 fun onScrollToTopCompleted() { val currentState = itemsLiveData.value ?: return itemsLiveData.value = currentState.copy(scrollToTop = false) } }
2. 修改Compose层滚动逻辑
将scrollToTop作为LaunchedEffect的依赖key,只要scrollToTop变为true就触发滚动,滚动完成后主动通知ViewModel复位状态:
@Composable fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) { val itemState by viewModel.itemsLiveData.observeAsState(ViewItemsState(emptyList(), false)) val listState = rememberLazyListState() // 把scrollToTop作为依赖key,值变化时自动触发执行 LaunchedEffect(itemState.scrollToTop) { if (itemState.scrollToTop) { Log.e("TAG", "TopCoinsScreen: scrollToTop" ) listState.scrollToItem(0) // 滚动完成后复位标记 viewModel.onScrollToTopCompleted() } } LazyColumn(state = listState) { items(itemState.items) { item -> ItemCompose( item.name, item.value ) } } }
3. 强制重建列表的极端场景方案
如果确实需要完全重置列表状态(包括滚动位置、子项缓存状态等),可以给列表设置版本key,每次需要重建时变更key即可,旧列表实例会被完全销毁重建:
// ViewModel新增listVersion字段,每次需要重置列表时版本号+1 class ViewItemsState( val items: List<ViewItem>, val scrollToTop: Boolean, val listVersion: Int // 每次重排/重置时自增 ) @Composable fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) { val itemState by viewModel.itemsLiveData.observeAsState(ViewItemsState(emptyList(), false, 0)) // 给listState设置版本key,版本变化时自动生成新的listState val listState = rememberLazyListState(key = itemState.listVersion) LazyColumn(state = listState) { items(itemState.items) { item -> ItemCompose( item.name, item.value ) } } }
该方案性能开销比单纯滚动更高,仅适合需要彻底清空列表所有状态的场景,普通滚动需求优先使用第一种方案即可。
内容的提问来源于stack exchange,提问作者Rafael
相关产品推荐
相关产品推荐

