无法阻止Canvas Composable的Draw Scope因动画持续触发的问题
问题分析
你在Box布局中嵌套了两个Composable:
BlinkingCircle:包含无限循环的圆形缩放动画,依赖animatableSize状态值Test:包含灰色背景Box和内部Canvas,目前仅打印日志
当圆形动画的animatableSize更新时,Test内部Canvas的onDraw会持续触发,日志重复打印。原因有两点:
- 重组传导:
animatableSize是状态值,它的变化会触发BlinkingCircle重组,进而导致父Box的内容lambda重新执行,Test会被再次调用(即使参数无变化),触发内部Canvas的onDraw。 - 全局重绘:动画运行时,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
相关产品推荐
相关产品推荐

