Flutter CustomPainter自动绘制属性repaint原理

这篇文章主要讨论的是Fluter中CustomPainter中使用repaint属性实现自动paint的原理。

首先使用CustomPaint创建一个StatefulWidget,demo中根据点击位置,对蓝色的圆进行位置变换。

代码如下

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中的源码就有答案
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上需要更新,具体做法就是设置私有变量_needsPainttrue

这个变量怎么用呢?通过对paint方法打断点,越过了各种Binding,找到了PipelineOwnerflushPaint方法。

芜,flushPaint在干嘛?很简单啊,把收集到的_nodesNeedingPaint全部拿出来画一遍。关于_nodesNeedingPaint怎么来的,看下面markNeedsPaint源码中,关于owner得属性有个add方法,从这边添加dirtyNode

  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的调用。

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

参考链接


发布者

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注