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

无法阻止Canvas Composable的Draw Scope因动画持续触发的问题

问题分析

你在Box布局中嵌套了两个Composable:

  • BlinkingCircle:包含无限循环的圆形缩放动画,依赖animatableSize状态值
  • Test:包含灰色背景Box和内部Canvas,目前仅打印日志

当圆形动画的animatableSize更新时,Test内部Canvas的onDraw会持续触发,日志重复打印。原因有两点:

  1. 重组传导:animatableSize是状态值,它的变化会触发BlinkingCircle重组,进而导致父Box的内容lambda重新执行,Test会被再次调用(即使参数无变化),触发内部Canvas的onDraw。
  2. 全局重绘:动画运行时,Compose会触发界面每一帧的重绘,即使Test没有重组,Canvas作为绘制组件也会响应全局重绘,执行onDraw。
解决方案

方案1:隔离重组作用域,避免Test被不必要重组

通过remember缓存Test的调用逻辑,确保父Box重组时不会重复执行Test的Compose流程:

Box(
    modifier = Modifier
        .fillMaxWidth(1f)
        .wrapContentHeight()
) {
    BlinkingCircle()
    // 用remember缓存Test的Composable实例,阻断重组传导
    val testContent = remember {
        @Composable {
            val modifier = Modifier.align(Alignment.Center)
            Test(modifier)
        }
    }
    testContent()
}

方案2:缓存Canvas绘制内容,避免无意义重绘

如果Test内部的Canvas绘制内容固定(无需动态更新),用Modifier.drawWithCache替代Canvas组件,缓存绘制结果,仅在依赖状态变化时重新绘制:

@Composable
fun Test(modifier: Modifier) {
    Box(
        modifier = modifier
            .background(Color.LightGray)
            .width(150.dp)
            .height(100.dp)
            .offset(300.dp, 50.dp)
            .then(
                Modifier
                    .width(50.dp)
                    .height(50.dp)
                    .drawWithCache {
                        // 缓存绘制逻辑,仅首次或依赖状态变化时执行
                        onDrawBehind {
                            println("Testing Box Text - 2")
                            // drawCircle(Color.Green, radius = 30f)
                        }
                    }
            )
    )
}

方案3:同时使用两种方案(推荐)

结合重组隔离和绘制缓存,彻底解决问题:

  • 用remember阻断Test的不必要重组
  • 用drawWithCache缓存Canvas绘制内容
修改后的完整代码示例
Box(
    modifier = Modifier
        .fillMaxWidth(1f)
        .wrapContentHeight()
) {
    BlinkingCircle()
    val testContent = remember {
        @Composable {
            val modifier = Modifier.align(Alignment.Center)
            Test(modifier)
        }
    }
    testContent()
}

@Composable
fun BlinkingCircle() {
    val screenWidthInDp = LocalConfiguration.current.screenWidthDp
    val infiniteScale = rememberInfiniteTransition()
    val animatableSize = infiniteScale.animateFloat(
        initialValue = 40f,
        targetValue = 6f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 1500,
                easing = FastOutLinearInEasing
            ),
            repeatMode = RepeatMode.Reverse
        )
    )

    val xStart = 57f
    val yStart = 126f

    Canvas(modifier = Modifier
        .width(screenWidthInDp.dp)
        .fillMaxHeight(1f),
        onDraw = {
            drawCircle(
                color = Color.White,
                radius = animatableSize.value,
                center = Offset(x = xStart, y = yStart)
            )
        }
    )
}

@Composable
fun Test(modifier: Modifier) {
    Box(
        modifier = modifier
            .background(Color.LightGray)
            .width(150.dp)
            .height(100.dp)
            .offset(300.dp, 50.dp)
            .then(
                Modifier
                    .width(50.dp)
                    .height(50.dp)
                    .drawWithCache {
                        onDrawBehind {
                            println("Testing Box Text - 2")
                            // drawCircle(Color.Green, radius = 30f)
                        }
                    }
            )
    )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 21:48:14