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
相关产品推荐
相关产品推荐

