为何ViewModel/全局存储的PagerState无法被HorizontalPager记住?
当尝试将PagerState从Composable移至ViewModel或全局存储时,HorizontalPager会丢失页面记忆——在屏幕旋转、窗口模式切换、深色模式变更等配置变更场景下,当前选中页面会重置为0。
可复现场景对比
- 选项1:使用ViewModel中存储的
PagerState,实例可在配置变更后存活,但页面仍会重置 - 选项2:在Composable Wrapper中使用
rememberPagerState(),配置变更后页面状态可正常保留
奇怪的是,选项1的打印显示PagerState对象地址未变,而选项2的对象地址每次配置变更都会变化,但后者却能正确保留页面状态。
复现代码
ViewModel代码
class TestViewModel: ViewModel() { @OptIn(ExperimentalFoundationApi::class) val pagerState = PagerState() init { println("ddd Initializing TestViewModel!") } }
MainActivity代码
class MainActivity : ComponentActivity() { private val vm: TestViewModel by viewModels() @OptIn(ExperimentalFoundationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PagerStateTheme { Wrapper(vm.pagerState) } } } }
Composable函数代码
@OptIn(ExperimentalFoundationApi::class) @Composable fun Wrapper(pagerState: PagerState) { val otherState = rememberPagerState() //Test(pagerState = pagerState) // option 1 //Test(pagerState = otherState) // option 2 } @OptIn(ExperimentalFoundationApi::class) @Composable fun Test(pagerState: PagerState) { println("ddd pager state object is $pagerState") HorizontalPager( pageCount = 4, state = pagerState, modifier = Modifier.fillMaxWidth(), pageSpacing = 8.dp, pageSize = PageSize.Fixed(300.dp), contentPadding = PaddingValues( start = 20.dp, end = 20.dp, top = 60.dp ), ) { pageIndex -> Box(modifier = Modifier.fillMaxSize()) { Text("Hello page $pageIndex") } } }
Gradle依赖配置
implementation platform('androidx.compose:compose-bom:2023.05.01') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' implementation "androidx.compose.runtime:runtime-livedata" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
原因分析
核心问题在于PagerState的状态恢复依赖Compose的**remember机制与SavedStateRegistry**:
选项2的
rememberPagerState()为何有效?rememberPagerState()内部不仅创建PagerState实例,还通过remember将其与Composable生命周期绑定,同时自动把页面状态(如当前选中页)注册到Compose的SavedStateRegistry中。配置变更时,SavedStateRegistry会自动保存并恢复这些状态——虽然每次变更会生成新的PagerState实例,但新实例会读取之前保存的状态值,因此页面位置得以保留。选项1的ViewModel存储为何失效?
直接在ViewModel中创建PagerState,虽然对象本身能在配置变更后存活,但该实例并未注册到Compose的SavedStateRegistry。当配置变更触发Compose重组时,HorizontalPager重新关联这个“存活”的PagerState,但实例内部的状态字段(如currentPage)并未被持久化,重组时会被重置为初始值0。
正确解决方案
不要在ViewModel中直接存储PagerState实例,而是存储页面索引的状态,再在Composable中用该状态初始化PagerState:
class TestViewModel : ViewModel() { private val _currentPage = mutableStateOf(0) val currentPage: State<Int> = _currentPage fun updateCurrentPage(page: Int) { _currentPage.value = page } } // 对应Composable代码 @OptIn(ExperimentalFoundationApi::class) @Composable fun Wrapper(vm: TestViewModel) { val pagerState = rememberPagerState(initialPage = vm.currentPage.value) // 同步PagerState的状态到ViewModel LaunchedEffect(pagerState.currentPage) { vm.updateCurrentPage(pagerState.currentPage) } Test(pagerState = pagerState) }
这种方式既利用ViewModel持久化状态,又通过Compose的rememberPagerState保证状态的正确恢复。
内容的提问来源于stack exchange,提问作者Grzegorz Adam Hankiewicz

