Jetpack Compose中Instagram风格故事指示器动画控制问题
解决Jetpack Compose Instagram故事组件滑动回退时的动画控制问题
核心问题分析
原代码的主要问题在于:
currentPage与PagerState.currentPage未同步,滑动页面时本地状态更新不及时- 指示器动画缺乏取消机制,切换页面后旧动画仍在运行,导致进度状态混乱
- 未明确区分“已完成”“当前播放”“未开始”三种指示器状态
解决方案代码
修改后的Stories组件
@OptIn(ExperimentalFoundationApi::class) @Composable fun Stories(data: List<Int>) { val pagerState = rememberPagerState( initialPage = 0, initialPageOffsetFraction = 0f ) var currentPage by remember { mutableStateOf(0) } val coScope = rememberCoroutineScope() // 同步PagerState的页面变化到本地currentPage LaunchedEffect(pagerState.currentPage) { currentPage = pagerState.currentPage } Box( modifier = Modifier .padding(16.dp) .clip(RoundedCornerShape(8.dp)) ) { Story(state = pagerState, data = data) Row( modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, top = 16.dp, end = 16.dp), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { data.forEachIndexed { index, _ -> Indicators( modifier = Modifier .height(4.dp) .weight(1f), isCurrentPage = currentPage == index, isCompleted = index < currentPage, onAnimateComplete = { coScope.launch { if (currentPage < data.size - 1) { currentPage++ pagerState.animateScrollToPage(currentPage) } } } ) } } } }
修改后的Indicators组件
@Composable fun Indicators( modifier: Modifier, isCurrentPage: Boolean, isCompleted: Boolean, onAnimateComplete: () -> Unit ) { var progress by remember { mutableStateOf(0f) } val animationScope = rememberCoroutineScope() var animationJob by remember { mutableStateOf<Job?>(null) } // 根据页面状态重置或启动动画 LaunchedEffect(isCurrentPage, isCompleted) { // 取消之前的动画,避免状态混乱 animationJob?.cancel() when { isCompleted -> { progress = 1f } isCurrentPage -> { progress = 0f // 启动新的进度动画,总时长5秒(可自定义) animationJob = animationScope.launch { val totalDuration = 5000L val startTime = System.currentTimeMillis() while (System.currentTimeMillis() - startTime < totalDuration) { progress = (System.currentTimeMillis() - startTime) / totalDuration.toFloat() delay(16) // 保持60fps平滑动画 } progress = 1f onAnimateComplete() } } else -> { progress = 0f } } } LinearProgressIndicator( modifier = modifier.clip(RoundedCornerShape(12.dp)), color = Color.White, trackColor = Color.Gray.copy(alpha = 0.5f), progress = progress ) }
Story组件保持不变
@OptIn(ExperimentalFoundationApi::class) @Composable fun Story(state: PagerState, data: List<Int>) { HorizontalPager( modifier = Modifier, state = state, pageCount = data.size ) { Image( modifier = Modifier.height(440.dp), painter = painterResource(id = data[it]), contentDescription = "StoryImage", contentScale = ContentScale.FillBounds, ) } }
关键修改说明
- 页面状态同步:添加
LaunchedEffect监听PagerState.currentPage,确保滑动页面时本地currentPage实时更新,让指示器状态与当前页面保持一致。 - 动画生命周期管理:在
Indicators中引入animationJob,每次状态变化时先取消旧动画,再根据当前状态启动新动画或重置进度,彻底解决切换页面后旧动画继续运行的问题。 - 状态明确区分:将指示器分为三种状态:
- 已完成(
isCompleted):进度直接设为1f - 当前页面(
isCurrentPage):从0开始播放进度动画 - 未开始:进度重置为0f
- 已完成(
- 平滑动画实现:改用系统时间计算进度,替代原有的固定步长累加,动画更平滑,且总时长可灵活调整(默认5秒与Instagram一致)。
内容的提问来源于stack exchange,提问作者Hrishi
相关产品推荐
相关产品推荐

