Jetpack Compose切换UI状态时如何保存HorizontalPager页面状态
问题:Compose切换UI状态后HorizontalPager无法恢复历史页面索引
我有两个UI状态:FIRST和SECOND。FIRST状态下界面包含HorizontalPager及其他组件;SECOND状态下仅显示简单的Text组件,通过点击Button在两种状态间切换。
需求:切换到SECOND状态时保存HorizontalPager的当前页面索引,切回FIRST状态时,HorizontalPager直接显示之前的页面。但目前切回FIRST状态时,HorizontalPager总是默认打开第0页。
注意:HorizontalPager的当前页面索引仅在Activity处于活跃状态时保存。Activity被销毁(如关闭应用或跳转其他Activity)后,重新创建时HorizontalPager会从第0页开始。
现有代码
MainActivity
class MainActivity : ComponentActivity() { @OptIn(ExperimentalFoundationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ViewObersableTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { val pagerState = rememberPagerState() SaveVariable(pagerState, VariableViewModel()) } } } } }
SaveVariable
@Composable @OptIn(ExperimentalFoundationApi::class) fun SaveVariable(pagerState: PagerState, viewModel: VariableViewModel) { LaunchedEffect(key1 = pagerState) { snapshotFlow { pagerState.currentPage }.collect { page -> viewModel.currentIndex = page } } LaunchedEffect(key1 = viewModel.setLastCurrentPage) { Log.e(">>> LaunchedEffect", "${viewModel.currentIndex}") } SaveVariableItem(viewModel.currentIndex, viewModel.uiState, pagerState) { viewModel.changeUIi() } }
VariableViewModel
class VariableViewModel : ViewModel() { var uiState by mutableStateOf(ScreenName.FIRST) var currentIndex by mutableStateOf(0) var setLastCurrentPage by mutableStateOf(false) fun changeUIi() { uiState = if (uiState == ScreenName.FIRST) { setLastCurrentPage = true ScreenName.SECOND } else { setLastCurrentPage = false ScreenName.FIRST } } }
ScreenName
enum class ScreenName { FIRST, SECOND }
SaveVariableItem
@OptIn(ExperimentalFoundationApi::class) @Composable fun SaveVariableItem( currentIndex: Int, uiState: ScreenName, pagerState: PagerState, changeUiState: () -> Unit, ) { Column { when (uiState) { ScreenName.FIRST -> { ScreenFirstView(pagerState) } ScreenName.SECOND -> { ScreenSecondView(currentIndex, "SECOND") } } ChangeUiStateButton(changeUiState) } }
ChangeUiStateButton
@Composable fun ChangeUiStateButton(changeUiState: () -> Unit) { Button(onClick = { changeUiState() }) { Text(text = "Add") } }
ScreenFirstView
@OptIn(ExperimentalFoundationApi::class) @Composable fun ScreenFirstView(pagerState: PagerState) { Column( modifier = Modifier .wrapContentSize() .background(Color.Black) ) { HorizontalPager( modifier = Modifier.padding(20.dp), pageCount = 10, state = pagerState ) { page -> Text( text = "Page: $page", modifier = Modifier.fillMaxWidth(), color = Color.White ) } } }
ScreenSecondView
@Composable fun ScreenSecondView(currentIndex: Int, screenImage: String) { Column( modifier = Modifier .wrapContentSize() .background(Color.Black) ) { Text(text = "$currentIndex ==== $screenImage", color = Color.White) } }
使用的Compose版本:
implementation(platform("androidx.compose:compose-bom:2023.05.01"))
问题分析
- ViewModel实例创建错误:直接通过
VariableViewModel()创建实例,导致ViewModel无法与Activity生命周期绑定,状态可能丢失。 - PagerState未主动恢复:虽然ViewModel保存了
currentIndex,但切换回FIRST状态时,没有将保存的索引同步到pagerState中,导致HorizontalPager使用初始的0页。 - 冗余状态变量:
setLastCurrentPage变量无实际作用,增加了逻辑复杂度。
解决方案
1. 正确获取ViewModel实例
在Compose中,使用viewModel()函数获取ViewModel实例,确保其生命周期与宿主组件(Activity)一致:
class MainActivity : ComponentActivity() { @OptIn(ExperimentalFoundationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ViewObersableTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { val viewModel: VariableViewModel = viewModel() // 初始化PagerState时使用ViewModel中保存的索引 val pagerState = rememberPagerState(initialPage = viewModel.currentIndex) SaveVariable(pagerState, viewModel) } } } } }
2. 监听UI状态切换,恢复Pager页面
在SaveVariable中添加LaunchedEffect监听uiState变化,当切回FIRST状态时,将保存的索引同步到pagerState:
@Composable @OptIn(ExperimentalFoundationApi::class) fun SaveVariable(pagerState: PagerState, viewModel: VariableViewModel) { // 监听Pager页面变化,实时保存到ViewModel LaunchedEffect(key1 = pagerState) { snapshotFlow { pagerState.currentPage }.collect { page -> viewModel.currentIndex = page } } // 监听UI状态切换,切回FIRST时恢复页面 LaunchedEffect(key1 = viewModel.uiState) { if (viewModel.uiState == ScreenName.FIRST) { // 使用animateScrollToPage实现平滑跳转,无需动画可改用scrollToPage pagerState.animateScrollToPage(viewModel.currentIndex) } } SaveVariableItem(viewModel.currentIndex, viewModel.uiState, pagerState) { viewModel.changeUIi() } }
3. 简化ViewModel逻辑
移除冗余的setLastCurrentPage变量,简化状态切换逻辑:
class VariableViewModel : ViewModel() { var uiState by mutableStateOf(ScreenName.FIRST) var currentIndex by mutableStateOf(0) fun changeUIi() { uiState = if (uiState == ScreenName.FIRST) { ScreenName.SECOND } else { ScreenName.FIRST } } }
验证效果
修改完成后,切换到SECOND状态再切回FIRST状态,HorizontalPager会自动跳转到之前停留的页面索引,满足需求。
内容的提问来源于stack exchange,提问作者Compose Learner
相关产品推荐
相关产品推荐

