Flutter RepaintBoundary重绘规则:_lastIndex变化下两示例行为差异
核心前提:Flutter渲染流程的两层独立逻辑
setState()触发的Widget树重建、配置同步,和渲染层实际执行重绘是完全解耦的流程:
- 重建流程只会重新生成Widget对象,把新的配置同步给对应的RenderObject,不会主动让RenderObject执行
paint()方法 - 只有当RenderObject被打上
needsPaint标记时,才会在下一帧的绘制阶段真正运行绘制逻辑RepaintBoundary的本质是绘制层的隔离边界,它会把内部的绘制内容拆成独立的合成层,只有边界内部的RenderObject被自身标记为needsPaint,这个合成层才会重新绘制,和上层Widget树有没有重建没有直接关系。
带Text示例触发重绘的原因
当CustomPaint嵌套了依赖_lastIndex值的Text子组件时,_lastIndex变化触发的重建流程会让Text对应的RenderObject拿到新的文本内容,框架会自动把这个Text的RenderObject标记为needsPaint。
由于Text是CustomPaint的直接子组件,CustomPaint对应的RenderCustomPaint的绘制区域包含了这个子组件,子组件的重绘请求会同步向上传导,把CustomPaint自身的RenderObject也标记为needsPaint。这时候哪怕MyPainter没有写任何重绘相关逻辑,整个CustomPaint的绘制流程都会重新执行,连带触发MyPainter.paint()方法运行——这也是你测试中观察到“放Text才会重绘”的本质原因,和painter本身没有关联。
无Text示例不触发重绘的原因
当CustomPaint没有嵌套依赖_lastIndex的子组件时,整个RepaintBoundary内部没有任何RenderObject会因为_lastIndex的变化被自动标记为needsPaint。
这时候哪怕你在build时传入了带新index的MyPainter实例,RenderCustomPaint的配置更新逻辑只会做两件事:
- 把新的painter实例赋值到自身的painter属性上
- 调用新painter的
shouldRepaint(oldPainter)方法,根据返回值判断要不要把自己标记为needsPaint
很多人自定义CustomPainter时,会在shouldRepaint里直接写死返回false,或者仅对比新旧painter的运行时类型而非内部属性,这时候shouldRepaint会返回false,RenderCustomPaint自然不会给自己打重绘标记,MyPainter.paint()也就不会被执行。这也是你观察到“单独传新的painter实例没有重绘效果”的核心原因。
满足需求的正确实现方式
不需要额外嵌套Text当重绘触发器,只要在自定义MyPainter时正确实现shouldRepaint的对比逻辑即可:
class MyPainter extends CustomPainter { final int index; const MyPainter({required this.index}); @override void paint(Canvas canvas, Size size) { // 自定义绘制逻辑 } @override bool shouldRepaint(covariant MyPainter oldDelegate) { // 只要传入的index发生变化,就返回true,主动触发重绘 return oldDelegate.index != index; } }
这种实现下,当setState()传入新的MyPainter实例时,RenderCustomPaint会通过shouldRepaint拿到需要重绘的信号,自动给自己打上needsPaint标记,完全不需要依赖RepaintBoundary内部的其他组件触发重绘,也能达到和Container一致的重建后重绘效果。
注意:测试中用到的
isComplex: true、willChange: false参数本身不会影响重绘触发逻辑,这两个参数只是给框架提供绘制优化提示,告诉框架这块内容是静态复杂图形、不需要频繁变化,和重绘触发条件无关。
内容的提问来源于stack exchange,提问作者Coding Glass

