使用DrawScope时出现不必要重绘:是否为重组?如何避免?
问题分析与解决方案
这是重组吗?
不是。先看官方定义:
重组是指当输入发生变化时,再次调用可组合函数的过程。仅当函数的输入变化时才会发生,Compose会仅调用可能变化的函数或lambda,跳过其余部分。
你遇到的情况是:点击按钮时,Canvas和带drawBehind的Text并没有被重新调用(也就是没触发重组),而是它们的绘制逻辑被重复执行了。原因很简单:按钮的波纹效果会触发父布局Column的重绘,而Canvas的绘制块、drawBehind的lambda都是在绘制阶段运行的,每次重绘都会重新执行这些代码,所以randomColor()会被再次调用,颜色就跟着变了。
而第三个用background modifier的Text,它的颜色参数是在重组阶段计算的,只有当这个Text发生重组时才会重新计算颜色,所以它的背景色不会跟着按钮点击变化。
怎么避免这种不必要的重绘/颜色变化?
关键是把随机颜色的计算从绘制阶段挪到重组阶段,并用remember把颜色缓存起来,这样颜色只会在首次加载或者你指定的条件变化时生成,不会每次重绘都重新计算。
修改后的代码:
private fun randomColor() = Color( Random.nextInt(256), Random.nextInt(256), Random.nextInt(256), alpha = 255 ) @Preview @Composable fun preview() { Column { Button(onClick = { /*nothing*/ }) { Text(text = "Do nothing") } // 用remember缓存Canvas的随机色 val canvasColor = remember { randomColor() } Canvas(modifier = Modifier .fillMaxWidth().height(100.dp)) { drawCircle(color = canvasColor, radius = size.minDimension*.4f) } // 用remember缓存drawBehind的随机色 val drawBehindColor = remember { randomColor() } Text( modifier = Modifier .drawBehind { drawRect(color = drawBehindColor) } .fillMaxWidth() .height(50.dp), text = "Text component with drawBehind" ) Text( modifier = Modifier .background(color = randomColor()) .fillMaxWidth() .height(50.dp), text = "Background color set on the modifier" ) } }
为啥这么改?
remember会在Composable第一次加载时计算颜色,之后就把这个值缓存起来,除非你给它添加的依赖项发生变化(这里没加依赖,所以颜色只会生成一次)。- 把颜色提前计算好缓存后,绘制阶段的代码只会使用缓存好的颜色,就算按钮触发重绘,颜色也不会变化,自然就避免了不必要的视觉跳动。
如果之后需要主动更新颜色(比如点击按钮让颜色变化),可以给remember加个依赖项,比如:
// 加个状态变量控制颜色更新 var refreshTrigger by remember { mutableStateOf(0) } val canvasColor = remember(refreshTrigger) { randomColor() } // 点击按钮时更新状态,触发颜色重新计算 Button(onClick = { refreshTrigger++ }) { Text(text = "Change Colors") }
内容的提问来源于stack exchange,提问作者ucMax
相关产品推荐
相关产品推荐

