Jetpack Compose中如何暂停Instagram故事式进度条动画
问题:Instagram故事风格横向进度条无法暂停动画
我正在实现类似Instagram故事的横向进度条,要求用户滚动切换故事或长按故事时暂停进度条动画。目前实现后,修改paused参数后,协程仍继续更新进度值,动画未暂停。尝试过使用Animatable并调用stop()方法也无效。
现有实现代码
HorizontalProgressBar 实现
fun HorizontalProgressBar( modifier: Modifier = Modifier, progressColor: Color = Color.White, backGroundColor: Color = Color.White.copy(0.14f), progressBarsState: ProgressBarsState = ProgressBarsState.NOT_STARTED, index: Int = 0, paused: Boolean = false, onSliceFinished: () -> Unit, ) { val initialValue = when (progressBarsState) { ProgressBarsState.COMPLETED -> 1f else -> 0f } var progress by remember { mutableStateOf(initialValue) } //using coroutine to increase progress value for animation purpose LaunchedEffect(key1 = paused) { if (paused){ cancel() } else { if (progressBarsState == ProgressBarsState.PLAYING) { while (progress < 1f) { progress += 0.01f delay(20) } onSliceFinished() } } } LinearProgressIndicator( progress = progress, modifier .height(2.dp) .clip(YouthAppTheme.shapes.medium), progressColor, backGroundColor ) }
外部传入paused参数逻辑
var scrollInProgress by remember { mutableStateOf(pagerState.isScrollInProgress) } SlicedProgressBar( 2, modifier = Modifier .fillMaxWidth() .height(48.dp), paused = scrollInProgress, spacing = 10.dp )
SlicedProgressBar 实现
@Composable fun SlicedProgressBar( pageCount: Int, modifier: Modifier, paused: Boolean, spacing: Dp, onSliceFinished: () -> Unit, ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = modifier.height(38.dp), ) { var currentIndex by remember { mutableStateOf(0) } for (index in 0 until pageCount) { Spacer(modifier = Modifier.width(20.dp)) val correctState = when { index < currentIndex -> ProgressBarsState.COMPLETED index == currentIndex -> ProgressBarsState.PLAYING else -> ProgressBarsState.NOT_STARTED } HorizontalProgressBar( modifier = Modifier.weight(1f), progressBarsState = correctState, index = currentIndex, paused = paused ) { onSliceFinished() currentIndex++ } } } }
问题分析
核心问题在于**LaunchedEffect的key设置不完整,且协程取消逻辑错误**:
- 当
paused从false变为true时,新的LaunchedEffect启动,但cancel()仅取消当前新协程,之前正在更新进度的旧协程未被终止——因为它是在paused=false时启动的,对应的LaunchedEffect已完成初始化,不会自动取消。 LaunchedEffect未监听progressBarsState变化,进度条状态切换时无法正确重启或终止协程。
修复方案
通过rememberCoroutineScope主动管理协程生命周期,同时让LaunchedEffect监听所有关键状态,确保状态变化时能终止旧协程、启动新协程:
修改后的 HorizontalProgressBar
fun HorizontalProgressBar( modifier: Modifier = Modifier, progressColor: Color = Color.White, backGroundColor: Color = Color.White.copy(0.14f), progressBarsState: ProgressBarsState = ProgressBarsState.NOT_STARTED, index: Int = 0, paused: Boolean = false, onSliceFinished: () -> Unit, ) { val initialValue = when (progressBarsState) { ProgressBarsState.COMPLETED -> 1f else -> 0f } var progress by remember { mutableStateOf(initialValue) } val coroutineScope = rememberCoroutineScope() var animationJob by remember { mutableStateOf<Job?>(null) } // 监听所有关键状态变化 LaunchedEffect(paused, progressBarsState, progress) { // 先取消之前的动画任务 animationJob?.cancel() if (!paused && progressBarsState == ProgressBarsState.PLAYING && progress < 1f) { animationJob = coroutineScope.launch { while (progress < 1f && !paused) { progress += 0.01f delay(20) } if (progress >= 1f) { onSliceFinished() } } } } LinearProgressIndicator( progress = progress, modifier .height(2.dp) .clip(YouthAppTheme.shapes.medium), progressColor = progressColor, backgroundColor = backGroundColor ) }
优化后的 SlicedProgressBar(同步页面状态)
@Composable fun SlicedProgressBar( pageCount: Int, modifier: Modifier, paused: Boolean, spacing: Dp, currentPage: Int, // 新增:传入当前页面索引,同步进度条状态 onSliceFinished: () -> Unit, ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = modifier.height(38.dp), ) { for (index in 0 until pageCount) { if (index != 0) { Spacer(modifier = Modifier.width(spacing)) // 复用传入的spacing参数 } val correctState = when { index < currentPage -> ProgressBarsState.COMPLETED index == currentPage -> ProgressBarsState.PLAYING else -> ProgressBarsState.NOT_STARTED } HorizontalProgressBar( modifier = Modifier.weight(1f), progressBarsState = correctState, paused = paused ) { onSliceFinished() } } } }
外部调用调整
val pagerState = rememberPagerState() var scrollInProgress by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() // 监听Pager滚动状态 LaunchedEffect(pagerState.isScrollInProgress) { scrollInProgress = pagerState.isScrollInProgress } SlicedProgressBar( pageCount = 2, modifier = Modifier.fillMaxWidth().height(48.dp), paused = scrollInProgress, spacing = 10.dp, currentPage = pagerState.currentPage, onSliceFinished = { // 自动切换下一页逻辑 coroutineScope.launch { pagerState.animateScrollToPage((pagerState.currentPage + 1) % 2) } } )
修复说明
- 协程主动管理:通过
animationJob跟踪动画协程,状态变化时主动取消旧任务,避免多协程同时更新进度。 - 完整状态监听:
LaunchedEffect的key包含paused、progressBarsState、progress,确保任何状态变化都会触发协程重新评估。 - 循环内实时判断:进度更新循环中加入
!paused判断,即使协程启动后paused变为true,也能立即停止进度更新。
内容的提问来源于stack exchange,提问作者Tornike Kachkachishvili
相关产品推荐
相关产品推荐

