这篇文章主要讨论的是Fluter中CustomPainter中使用repaint属性实现自动paint的原理。
首先使用CustomPaint创建一个StatefulWidget,demo中根据点击位置,对蓝色的圆进行位置变换。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import 'package:flutter/material.dart'; void main() => runApp(MyPaint()); class MyPaint extends StatefulWidget { const MyPaint({Key? key}) : super(key: key); @override _MyPaintState createState() => _MyPaintState(); } class _MyPaintState extends State<MyPaint> { ValueNotifier<double> _vn = ValueNotifier<double>(0); @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: parse, child: CustomPaint( painter: BoxPainter(vn: _vn), ), ); } dynamic parse(DragUpdateDetails details) { _vn.value = details.globalPosition.dy; } } class BoxPainter extends CustomPainter { ValueNotifier<double> vn; BoxPainter({@required this.vn}) : super(repaint: vn); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, 0); canvas.drawCircle(Offset(0, vn.value), 66, Paint()..color = Colors.blue); } @override bool shouldRepaint(BoxPainter oldDelegate) => false; @override bool shouldRebuildSemantics(BoxPainter oldDelegate) => false; } |
Repaint如何工作?
CustomPainter中的源码就有答案
1 2 3 4 5 6 7 8 9 10 |
abstract class CustomPainter extends Listenable { final Listenable? _repaint; @override void addListener(VoidCallback listener) => _repaint?.addListener(listener); @override void removeListener(VoidCallback listener) => _repaint?.removeListener(listener); } |
这个时候_repaint
是一个ValueNotifier,那到底是谁注册了listener,什么时候注册,什么时候销毁呢?
啥时候注册?那就给addListener打个断点,查查调用栈。
这个时候就顺腾摸瓜找到CustomPaint(是Paint不是Painter)创建的RenderCustomPaint
,在attach和detach中,进行了注册和销毁。
markNeedsPaint和PipelineOwner是啥?
深入浅出 Flutter Framework 之 PipelineOwner | 雪峰的blog (zxfcumtcs.github.io)
对于PipelineOwner,暂时只能这么说:
那么markNeedsPaint
是什么?这是一个RenderObject的方法,标记该RenderObject在RenderTree上需要更新,具体做法就是设置私有变量_needsPaint
为true
。
这个变量怎么用呢?通过对paint方法打断点,越过了各种Binding,找到了PipelineOwner
的flushPaint
方法。
芜,flushPaint在干嘛?很简单啊,把收集到的_nodesNeedingPaint
全部拿出来画一遍。关于_nodesNeedingPaint
怎么来的,看下面markNeedsPaint
源码中,关于owner
得属性有个add方法,从这边添加dirtyNode
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void flushPaint() { //...省略 似乎是控制同步的代码 final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; // Sort the dirty nodes in reverse order (deepest first). for (final RenderObject node in dirtyNodes ..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { assert(node._layer != null); if (node._needsPaint && node.owner == this) { if (node._layer!.attached) { print(node); //查查对应的RenderObject到底是谁 PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } assert(_nodesNeedingPaint.isEmpty); } //...省略 似乎是控制同步的代码 |
注意到代码中我自己添加了一行print,终端输出结果为I/flutter ( 7249): RenderView#ae16c NEEDS-PAINT
,并不是上文提到的RenderCustomPaint
,为什么呢,原因还在markNeedsPaint
的源码里面。
注意到这一行,做了对父级markNeedsPaint
的调用。
1 2 3 |
else if (parent is RenderObject){ parent.markNeedsPaint(); } |
好奇现在的RenderTree结构?使用 debugDumpRenderTree
方法把RenderTreeDump出来。
原因一目了然了,自下向上mark到了RenderView
。RenderView是啥?是所有RenderTree的根。最后还有一个isRepaintBoundary
属性,这边简单的解释一下:有时候在RenderTree中会见到 RenderRepaintBoundary class - rendering library - Dart API (flutter.dev),这个作用是用作优化,即当你的子RenderObject的绘制不会影响到父RenderObject的绘制时,插入RenderRepaintBoundary保证局部重绘,避免上面说的一直向上调用parent.markNeedPaint
。