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

Jetpack Compose:如何将Box实现的脉冲动画转为Canvas实现?

使用Canvas重构Jetpack Compose脉冲动画

直接复用你原有的动画逻辑,将Box实现转换成Canvas版本,这样能减少Compose节点数量,提升动画性能,尤其是复杂场景下效果更明显。

完整Canvas实现代码

@Composable
fun PulsatingCirclesWithCanvas() {
    val infiniteTransition = rememberInfiniteTransition()
    // 保留原动画逻辑,获取动态变化的dp值
    val mediumCircleSizeDp by infiniteTransition.animateValue(
        initialValue = 26.dp,
        targetValue = 25.dp,
        Dp.VectorConverter,
        animationSpec = infiniteRepeatable(
            animation = tween(500, easing = FastOutLinearInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )
    val smallCircleSizeDp by infiniteTransition.animateValue(
        initialValue = 18.dp,
        targetValue = 20.dp,
        Dp.VectorConverter,
        animationSpec = infiniteRepeatable(
            animation = tween(1000, easing = FastOutLinearInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )
    val fixedCircleSizeDp = 10.dp

    // 把dp转换成Canvas需要的像素单位
    val density = LocalDensity.current
    val mediumCircleSizePx = density.run { mediumCircleSizeDp.toPx() }
    val smallCircleSizePx = density.run { smallCircleSizeDp.toPx() }
    val fixedCircleSizePx = density.run { fixedCircleSizeDp.toPx() }
    val primaryColor = MaterialTheme.colors.primary

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(27.dp),
        onDraw = {
            // 计算Canvas中心作为所有圆的圆心
            val center = Offset(size.width / 2, size.height / 2)

            // 按层级绘制:底层中等脉冲圆 → 中间小脉冲圆 → 上层固定实心圆
            drawCircle(
                color = primaryColor.copy(alpha = 0.25f),
                radius = mediumCircleSizePx / 2, // drawCircle的radius是半径,原Box的size是直径,所以要除以2
                center = center
            )
            drawCircle(
                color = primaryColor.copy(alpha = 0.25f),
                radius = smallCircleSizePx / 2,
                center = center
            )
            drawCircle(
                color = primaryColor,
                radius = fixedCircleSizePx / 2,
                center = center
            )
        }
    )
}

@Preview(showBackground = true)
@Composable
fun PreviewPulsatingCirclesWithCanvas() {
    PulsatingCirclesWithCanvas()
}

关键注意事项

  • 单位转换:Canvas所有绘制参数用像素为单位,必须通过LocalDensity把dp转成px,否则绘制大小会偏离预期
  • 半径计算:原Box的size是圆的直径,而drawCircle的radius参数是半径,所以要除以2才能和原效果一致
  • 绘制顺序:Canvas按代码执行顺序叠加绘制内容,先画的在底层,所以要保持原Box的层级顺序
  • 动画复用:完全保留了你原有的无限动画逻辑,确保动画节奏、缓动效果和Box版本完全一致

整合到你的NodeView

如果需要把脉冲动画加到你现有的NodeView中,可以参考这个修改版:

@Composable
fun NodeViewWithPulse(
    showTopLine: Boolean,
    showBottomLine: Boolean,
) {
    val infiniteTransition = rememberInfiniteTransition()
    // 自定义脉冲圆的动画参数
    val pulseCircleSizeDp by infiniteTransition.animateValue(
        initialValue = 18.dp,
        targetValue = 24.dp,
        Dp.VectorConverter,
        animationSpec = infiniteRepeatable(
            animation = tween(1000, easing = FastOutLinearInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    val density = LocalDensity.current
    val circleSize = density.run { 8.dp.toPx() }
    val strokeWidth = density.run { 2.dp.toPx() }
    val pulseCircleSizePx = density.run { pulseCircleSizeDp.toPx() }
    val brush = Brush.linearGradient(listOf(Color.Red, Color.Blue))
    val center = Offset(size.width / 2, size.height / 2)

    Canvas(modifier = Modifier.fillMaxHeight()) {
        // 先绘制半透明脉冲圆(底层)
        drawCircle(
            color = Color.Red.copy(alpha = 0.2f),
            radius = pulseCircleSizePx / 2,
            center = center
        )
        // 绘制固定实心圆
        drawCircle(
            color = Color.Red,
            radius = circleSize / 2,
            center = center
        )
        // 绘制上下连接线
        if (showTopLine) {
            drawLine(
                brush = brush,
                start = Offset(x = size.width / 2, y = 0F),
                end = center,
                strokeWidth = strokeWidth,
            )
        }
        if (showBottomLine) {
            drawLine(
                brush = brush,
                start = center,
                end = Offset(x = size.width / 2, y = size.height),
                strokeWidth = strokeWidth,
            )
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:45:21