setState:
绘制过程:
WidgetsBinding.drawFrame:
void drawFrame() {buildOwner!.buildScope(renderViewElement!); //重新构建widget树pipelineOwner.flushLayout(); // 更新布局pipelineOwner.flushCompositingBits(); //更新合成信息pipelineOwner.flushPaint(); // 更新绘制if (sendFramesToEngine) {renderView.compositeFrame(); // 上屏,会将绘制出的bit数据发送给GPUpipelineOwner.flushSemantics(); // this also sends the semantics to the OS._firstFrameSent = true;}}
renderView.compositeFrame
正是完成了这个使命。以上,便是setState被调用到UI更的大概更新过程,实际的流程会更复杂一些,比如在build过程中是不允许再调用setState的,框架需要做一些检查;又比如在frame中会涉及到动画的调度、在上屏时会将所有的Layer添加到场景(Scene)对象后,再渲染Scene,读者有兴趣可以自行查看源码或关注笔者博客(后续会有补充内容哦)。
我们先定义一个绘制组件所占区域边框的画笔:
class OutlinePainter extends CustomPainter {@overridevoid paint(Canvas canvas, Size size) {print("paint");var paint = Paint()..strokeWidth = 2..style= PaintingStyle.stroke..color = Colors.black;canvas.drawRect(Offset.zero & size, paint);}// 本例中,rebuild时,painter会重新构建一个新实例,返回false,// 表示即使Painter实例发生变化也不需要重新绘制。@overridebool shouldRepaint(covariant CustomPainter oldDelegate) => false;}
测试用例如下:
class _RepaintBoundaryTestState extends State<RepaintBoundaryTest> {@overrideWidget build(BuildContext context) {return Column(children: [CustomPaint(size: Size(50, 50),painter: OutlinePainter(),),ElevatedButton(onPressed: () => setState(() {}),child: Text("setState"),)],);}}
此时我们点击“setState”按钮,会发现控制台输出了多次“paint”。这是因为ElevatedButton按钮点击时会执行水波动画,动画的每一帧都会触发一次ElevatedButton重绘(markNeedsRepaint),虽然重绘是ElevatedButton触发,但是我们前面说过markNeedsRepaint在执行过程中会向父级查找最近的一个 isRepaintBoundary 为 true 的节点,然后会在flushPaint时会创建一个layer,然后会从该父级节点出发向下绘制及其所有子节点,直到遇到一个 isRepaintBoundary为true的节点时停止向下查找绘制。所以在本例中,最终CustomPaint也重新绘制了。那如何防止CustomPaint被牵连呢?有两个方法:
RepaintBoundary
。RepaintBoundary
。RepaintBoundary 是一个可以有单个孩子的widget,实现原理很简单,就是将自己renderObject的isRepaintBoundary设为true,这样在绘制的过程中就会为其(以及其子节点)单独生成一个layer,子节点触发重绘时,RepaintBoundary为其最近的isRepaintBoundary为true的父节点,所以会从RepaintBoundary向下绘制,如此,CustomPaint和 ElevatedButton的重绘就不会相互牵连了。其实从名字上也能看出来RepaintBoundary 的作用其实就是一个“绘制边界”。上述绘制逻辑的实现在PaintContext方法中,感兴趣的话可以自己查阅源码。
同理,在layout阶段,LayoutBoundary的效果也类似。