You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ViewModel/全局存储的PagerState无法被HorizontalPager记住?

PagerState存入ViewModel后页面状态丢失的原因解析

当尝试将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**:

  1. 选项2的rememberPagerState()为何有效?
    rememberPagerState()内部不仅创建PagerState实例,还通过remember将其与Composable生命周期绑定,同时自动把页面状态(如当前选中页)注册到Compose的SavedStateRegistry中。配置变更时,SavedStateRegistry会自动保存并恢复这些状态——虽然每次变更会生成新的PagerState实例,但新实例会读取之前保存的状态值,因此页面位置得以保留。

  2. 选项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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:35:33