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

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设置不完整,且协程取消逻辑错误**:

  1. 当paused从false变为true时,新的LaunchedEffect启动,但cancel()仅取消当前新协程,之前正在更新进度的旧协程未被终止——因为它是在paused=false时启动的,对应的LaunchedEffect已完成初始化,不会自动取消。
  2. 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)
        }
    }
)

修复说明

  1. 协程主动管理:通过animationJob跟踪动画协程,状态变化时主动取消旧任务,避免多协程同时更新进度。
  2. 完整状态监听:LaunchedEffect的key包含paused、progressBarsState、progress,确保任何状态变化都会触发协程重新评估。
  3. 循环内实时判断:进度更新循环中加入!paused判断,即使协程启动后paused变为true,也能立即停止进度更新。

内容的提问来源于stack exchange,提问作者Tornike Kachkachishvili

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:55:13