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

Jetpack Compose动画求助:可控多圆圈脉动动画实现问题

可控制的多圆圈脉动动画实现方案

针对你遇到的InfiniteTransition无限循环不符合需求、UpdateTransition缺少动画结束监听的问题,以下是几种适配Jetpack Compose的解决方案:

1. 为UpdateTransition添加动画结束监听

UpdateTransition没有直接的onEnd回调,但可以通过LaunchedEffect结合状态监听实现动画结束逻辑。当transition.currentState与transition.targetState一致时,说明当前动画已完成。

enum class CircleState { Small, Large }

@Composable
fun ControlledCircleAnimation() {
    var circleState by remember { mutableStateOf(CircleState.Small) }
    val transition = updateTransition(targetState = circleState, label = "Circle Transition")

    val color by transition.animateColor(label = "Color") { state ->
        when (state) {
            CircleState.Small -> Color.Blue
            CircleState.Large -> Color.Red
        }
    }

    val size by transition.animateDp(label = "Size") { state ->
        when (state) {
            CircleState.Small -> 100.dp
            CircleState.Large -> 200.dp
        }
    }

    // 监听动画完成事件
    LaunchedEffect(transition) {
        snapshotFlow { transition.currentState == transition.targetState }
            .filter { it }
            .collect {
                // 这里可以添加动画结束后的逻辑:切换状态启动下一轮,或停止动画
                circleState = when (circleState) {
                    CircleState.Small -> CircleState.Large
                    CircleState.Large -> CircleState.Small
                }
                // 若需要限制循环次数,可添加计数器判断后停止
            }
    }

    Box(
        Modifier
            .clip(CircleShape)
            .size(size)
            .background(color)
            .clickable {
                circleState = when (circleState) {
                    CircleState.Small -> CircleState.Large
                    CircleState.Large -> CircleState.Small
                }
            }
    )
}

2. 实现多圆圈顺序脉动动画

要让多个圆圈依次脉动(完成一个再启动下一个),可以维护索引变量跟踪当前活跃圆圈,配合计数器控制总循环次数:

data class CircleConfig(
    val color: Color,
    val initialScale: Float,
    val targetScale: Float,
    val duration: Int
)

@Composable
fun MultiCirclePulseAnimation() {
    val circles = remember {
        listOf(
            CircleConfig(Color.Blue.copy(0.7f), 0.5f, 1.2f, 1000),
            CircleConfig(Color.Green.copy(0.7f), 0.6f, 1.3f, 1200),
            CircleConfig(Color.Red.copy(0.7f), 0.7f, 1.4f, 1400)
        )
    }
    var currentCircleIndex by remember { mutableStateOf(0) }
    var isAnimating by remember { mutableStateOf(true) }
    val maxCycles = 3 // 控制动画总循环次数
    var cycleCount by remember { mutableStateOf(0) }

    val transition = updateTransition(
        targetState = currentCircleIndex,
        label = "Multi Circle Transition"
    )

    val scale by transition.animateFloat(
        label = "Scale",
        transitionSpec = { tween(circles[targetState].duration, easing = LinearEasing) }
    ) { index ->
        if (transition.currentState == index) circles[index].targetScale else circles[index].initialScale
    }

    // 切换下一个圆圈的动画
    LaunchedEffect(transition) {
        snapshotFlow { transition.currentState == transition.targetState }
            .filter { it && isAnimating }
            .collect {
                if (currentCircleIndex == circles.lastIndex) {
                    cycleCount++
                    if (cycleCount >= maxCycles) {
                        isAnimating = false
                        return@collect
                    }
                    currentCircleIndex = 0
                } else {
                    currentCircleIndex++
                }
            }
    }

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        circles.forEachIndexed { index, config ->
            Canvas(
                modifier = Modifier
                    .size(200.dp)
                    .graphicsLayer {
                        scaleX = if (index == currentCircleIndex && isAnimating) scale else config.initialScale
                        scaleY = if (index == currentCircleIndex && isAnimating) scale else config.initialScale
                    }
            ) {
                drawCircle(
                    color = config.color,
                    center = center,
                    radius = size.minDimension / 2
                )
            }
        }
    }
}

3. 替代方案:animateAsState + finishedListener

如果UpdateTransition的方式不够灵活,可使用animateAsState的finishedListener直接监听单个动画结束,手动控制序列:

@Composable
fun SequentialCirclePulse() {
    var isCircle1Animating by remember { mutableStateOf(true) }
    var isCircle2Animating by remember { mutableStateOf(false) }
    var isCircle3Animating by remember { mutableStateOf(false) }
    var cycleCount by remember { mutableStateOf(0) }
    val maxCycles = 3

    val circle1Scale by animateAsState(
        targetValue = if (isCircle1Animating) 1.2f else 0.5f,
        animationSpec = tween(1000),
        finishedListener = {
            isCircle1Animating = false
            isCircle2Animating = true
        }
    )
    val circle2Scale by animateAsState(
        targetValue = if (isCircle2Animating) 1.3f else 0.6f,
        animationSpec = tween(1200),
        finishedListener = {
            isCircle2Animating = false
            isCircle3Animating = true
        }
    )
    val circle3Scale by animateAsState(
        targetValue = if (isCircle3Animating) 1.4f else 0.7f,
        animationSpec = tween(1400),
        finishedListener = {
            isCircle3Animating = false
            cycleCount++
            if (cycleCount < maxCycles) {
                isCircle1Animating = true
            }
        }
    )

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Canvas(modifier = Modifier.size(200.dp).graphicsLayer { scaleX = circle1Scale; scaleY = circle1Scale }) {
            drawCircle(Color.Blue.copy(0.7f), center, size.minDimension/2)
        }
        Canvas(modifier = Modifier.size(200.dp).graphicsLayer { scaleX = circle2Scale; scaleY = circle2Scale }) {
            drawCircle(Color.Green.copy(0.7f), center, size.minDimension/2)
        }
        Canvas(modifier = Modifier.size(200.dp).graphicsLayer { scaleX = circle3Scale; scaleY = circle3Scale }) {
            drawCircle(Color.Red.copy(0.7f), center, size.minDimension/2)
        }
    }
}

核心要点

  • UpdateTransition通过snapshotFlow监听状态匹配实现结束回调
  • 多圆圈动画通过索引变量和计数器控制顺序与循环次数
  • animateAsState的finishedListener适合简单序列动画的直接控制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 20:51:13