Flutter状态更新疑问:为何重建所有Widget?无参Stateless CustomPainter为何受影响?
1. 为何更新State会导致所有Widget发生重建?
调用setState()时,Flutter会把对应的State标记为脏状态(dirty),随即触发该StatefulWidget的build()方法重新执行。build()的核心作用是生成Widget树,因此每次执行build(),方法内定义的所有Widget都会重新创建实例(也就是所谓的“重建”)。
需要明确:Widget重建≠实际渲染更新。Flutter的Element树会通过diff算法对比新旧Widget的类型、key等信息,只有当Widget的类型或配置发生变化时,才会更新对应的Element和RenderObject,最终触发UI的实际渲染更新。这种重建通常是轻量操作,大部分场景下不会带来性能问题。
2. 兄弟组件调用setState时,未接收父参数的StatelessWidget(内含CustomPainter)为何更新?
你的代码中,MyBackground是在App组件的build()方法里创建的。当App的setState()触发时,App的build()重新执行,MyBackground会被重新实例化,它的build()方法也会再次调用,导致内部的CustomPaint被重新创建。
更关键的问题是:每次MyBackground.build()执行时,都会新建一个MyPainter()实例。CustomPaint组件会对比当前的painter和旧的painter是否为同一个对象——因为每次都是新实例,即便shouldRepaint返回false,CustomPaint依然会判定需要更新,进而调用paint()方法,导致你看到的背景颜色变化和debugPrint输出。
解决这个问题的两种常用方法:
- 缓存
MyPainter实例:比如在MyBackground中定义静态的MyPainter实例,或者将MyBackground改为StatefulWidget,把MyPainter实例存在State中,避免每次build都新建实例。 - 给
MyPainter添加const构造函数,将其变为不可变对象,然后在CustomPaint中使用const MyPainter(),这样每次build时都会复用同一个实例,此时shouldRepaint返回false就会生效。
内容的提问来源于stack exchange,提问作者Burhan Khanzada

