Android Compose HorizontalPager数据源与PagerState不一致问题解决
解决HorizontalPager数据源与PagerState不一致的索引越界问题
问题根源
删除页面后,pageDatas的长度会减少,但PagerState的currentPage不会自动同步更新。比如原来有2个页面,当前在第2页(索引1),删除后pageDatas长度变为1,此时currentPage还是1,直接用pageDatas[pagerState.currentPage]就会触发IndexOutOfBoundsException。另外,CustomView直接依赖未做安全校验的索引访问,重组时机不匹配也会加剧这个问题。
解决方案
1. 删除页面后同步调整PagerState的currentPage
删除操作完成后,检查当前currentPage是否超出新的数据源范围,如果超出则滚动到有效索引:
Button(onClick = { val currentPage = pagerState.currentPage pageDatas.removeAt(currentPage) // 调整currentPage到有效范围 if (pageDatas.isNotEmpty()) { val newPage = min(currentPage, pageDatas.size - 1) if (newPage != pagerState.currentPage) { coroutineScope.launch { pagerState.scrollToPage(newPage) } } } }) { Text("Delete") }
这里用coroutineScope是因为scrollToPage是挂起函数,需要在协程中调用。
2. 安全访问数据源索引
不要直接用pagerState.currentPage访问pageDatas,可以通过derivedStateOf创建安全的派生状态,自动处理边界情况:
val currentPageData by remember { derivedStateOf { pageDatas.getOrNull(pagerState.currentPage) ?: PageData(state = 0) // 提供默认兜底值 } } // 直接使用派生状态给CustomView CustomView(state = currentPageData.state)
derivedStateOf会自动监听pageDatas和pagerState.currentPage的变化,返回有效的数据,彻底避免越界问题。
3. 完整优化后代码
@Composable fun Screen() { val pageDatas = remember { mutableStateListOf( PageData(state = 0), PageData(state = 1) // 示例多一个页面方便测试 ) } val pagerState = rememberPagerState() val coroutineScope = rememberCoroutineScope() HorizontalPager(pageCount = pageDatas.size, state = pagerState) { index -> // Pager内部的索引始终有效,直接访问即可 Text("Page ${pageDatas[index].state}") } Button(onClick = { val currentPage = pagerState.currentPage if (pageDatas.isEmpty()) return@Button // 空数据时不执行删除 pageDatas.removeAt(currentPage) // 调整currentPage到有效范围 pageDatas.takeIf { it.isNotEmpty() }?.let { val newPage = min(currentPage, it.size - 1) if (newPage != pagerState.currentPage) { coroutineScope.launch { pagerState.scrollToPage(newPage) } } } }) { Text("Delete") } // 安全获取当前页面数据 val currentPageData by remember { derivedStateOf { pageDatas.getOrNull(pagerState.currentPage) ?: PageData(state = 0) } } CustomView(state = currentPageData.state) } data class PageData(val state: Int) @Composable fun CustomView(state: Int) { Text("CustomView State: $state") }
关键说明
HorizontalPager的pageCount绑定pageDatas.size,所以Pager内部渲染的索引始终是有效的,不用担心越界。- 删除后强制同步
PagerState的currentPage,确保后续所有依赖这个值的地方都能拿到有效索引。 - 用
derivedStateOf处理CustomView的状态依赖,比手动用LaunchedEffect更简洁,且能自动处理状态变化的同步。
内容的提问来源于stack exchange,提问作者HeiHa
相关产品推荐
相关产品推荐

