Android Jetpack Compose页面切换时保存滚动位置的问题(无需ViewModel)
我帮你分析下问题出在哪,再给你一个不用ViewModel的简单解决方案:
问题根源
你当前用rememberSaveable在ContentScreen内部保存ScrollState,但当你从ContentScreen返回WelcomeScreen时,ContentScreen对应的Composable实例会被销毁,它的rememberSaveable状态也会跟着被清除——因为rememberSaveable的作用域是当前Composable对应的导航栈条目(NavBackStackEntry),一旦这个条目被弹出栈,绑定的状态就没了。所以再次进入ContentScreen时,会重新初始化ScrollState到0,回到顶部。
解决方案:提升状态作用域
我们只需要把滚动状态的保存位置提升到比NavHost更持久的作用域(也就是MainScreen的作用域,只要MainScreen存在,这个状态就不会被销毁),然后把状态传递给ContentScreen即可,完全不用ViewModel:
1. 修改MainScreen,在父作用域保存滚动状态
把ScrollState的创建和保存移到MainScreen里,这样它的生命周期和整个主页面绑定:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( navController: NavHostController = rememberNavController(), ) { // 把滚动状态提升到MainScreen作用域,用rememberSaveable持久化 val contentScrollState = rememberSaveable(saver = ScrollState.Saver) { ScrollState(initial = 0) } Scaffold( ) { innerPadding -> NavHost( navController = navController, startDestination = WelcomeDestination.route, ) { composable(route = WelcomeDestination.route) { WelcomeScreen( onNavigationButtonClick = { navigationDestination: NavigationDestination -> navController.navigate(navigationDestination.route) }, modifier = Modifier .padding(innerPadding) .fillMaxSize() .wrapContentSize(Alignment.Center) ) } composable(route = ContentDestination.route) { ContentScreen( scrollState = contentScrollState, // 把状态传递给ContentScreen modifier = Modifier .padding(innerPadding) .fillMaxSize() .wrapContentSize(Alignment.Center) ) } } } }
2. 修改ContentScreen,接收外部传入的状态
不再在ContentScreen内部创建ScrollState,直接使用从父组件传入的已保存状态:
@Composable fun ContentScreen( scrollState: ScrollState, // 接收外部传入的滚动状态 modifier: Modifier ) { Box(modifier = modifier.verticalScroll(scrollState)) { Text(LONG_TEXT) } }
效果验证
现在你再测试:进入ContentScreen滚动到下方,返回WelcomeScreen,再进入ContentScreen,就能回到之前的滚动位置了。
另外补充个小细节:如果你的App有多个需要独立保存滚动位置的页面场景,也可以用NavBackStackEntry的savedStateHandle来存储,但这个场景下提升作用域是最简单直接的方式。
内容来源于stack exchange

