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

如何让LaunchedEffect执行指定次数?Jetpack Compose循环动画实现

解决方案:实现点击按钮后动画重复执行3次

问题背景

点击START按钮后,圆形组件需按指定时长完成3次缩放循环动画,原代码无法实现重复执行逻辑。

修改后的完整代码

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ProgressBarTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    BallScaleIndicator()
                }
            }
        }
    }
}

@Composable
fun BallScaleIndicator() {
    var isAnimating by remember { mutableStateOf(false) }
    var animationCycleCount by remember { mutableStateOf(0) }

    val animationProgressForText by animateFloatAsState(
        targetValue = if (isAnimating) 1f else 0.0f,
        animationSpec = keyframes {
            durationMillis = 12000 // 单次循环时长:放大4s + 保持2s + 缩小4s
            0.0f at 0
            0.8f at 4000
            0.8f at 6000
            1f at 12000
        },
        finishedListener = {
            // 单次动画结束后,减少循环计数,若还有剩余则重启动画
            if (animationCycleCount > 0) {
                animationCycleCount -= 1
                isAnimating = false
                // 延迟一小段时间后重启,避免状态切换太快导致动画不触发
                LaunchedEffect(Unit) {
                    delay(10)
                    isAnimating = true
                }
            } else {
                isAnimating = false
            }
        },
        label = "textAnimation"
    )

    val animationProgress by animateFloatAsState(
        targetValue = if (isAnimating) 0.8f else 0.6f,
        animationSpec = keyframes {
            durationMillis = 12000
            0.6f at 0
            0.8f at 4000
            0.8f at 6000
            0.6f at 12000
        },
        label = "scaleAnimation"
    )

    Column {
        Button(onClick = {
            // 点击开始时重置循环计数并启动动画
            animationCycleCount = 3
            isAnimating = true
        }) {
            Text(text = if (isAnimating) "RUNNING..." else "START")
        }

        Ball(
            modifier = Modifier
                .size(500.dp)
                .padding(16.dp)
                .scale(animationProgress),
            animationProgress = animationProgressForText
        )
    }
}

@Composable
fun Ball(
    modifier: Modifier = Modifier,
    borderColor: Color = Color.Black,
    borderWidth: Dp = 40.dp,
    animationProgress : Float
) {
    Box(
        modifier = modifier,
        contentAlignment = Alignment.Center
    ) {
        Canvas(
            modifier = modifier,
            onDraw = {
                val canvasSize = size.minDimension
                val circleRadius = (canvasSize - borderWidth.toPx()) / 2
                val circleCenter = Offset(size.width / 2, size.height / 2)

                drawCircle(
                    color = borderColor,
                    center = circleCenter,
                    radius = circleRadius,
                    style = Stroke(width = borderWidth.toPx())
                )
            }
        )

        Text(
            text = when {
                animationProgress > 0.0 && animationProgress < 0.8 -> "TAKE BREATH"
                animationProgress in 0.8..0.8000001 -> "HOLD ON"
                animationProgress == 0.0f -> "START"
                else -> "GIVE BREATH"
            },
            fontSize = 35.sp,
            color = Color.Black
        )
    }
}

关键修改说明

  • 状态管理优化:新增isAnimating控制动画启停,animationCycleCount记录剩余循环次数,替代原有的isScalingUp单一状态,更清晰控制循环逻辑。
  • 动画结束回调:利用animateFloatAsState的finishedListener,在单次动画完成后自动减少循环计数,若还有剩余次数则重启动画,直到完成3次循环。
  • 按钮状态更新:按钮文本根据动画运行状态切换为"RUNNING..."或"START",提升交互体验。
  • 移除无效代码:删除原代码中LaunchedEffect外直接修改isScalingUp = false的错误逻辑,避免动画被强制中断。

内容的提问来源于stack exchange,提问作者Yunus Emrah Uluçay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:43:14