阅读 1161

Flutter 绘制探索 2 | 全面分析 CustomPainter 相关类 | 七日打卡

零:前言

1. 系列引言

可能说起 Flutter 绘制,大家第一反应就是用 CustomPaint 组件,自定义 CustomPainter 对象来画。Flutter 中所有可以看得到的组件,比如 Text、Image、Switch、Slider 等等,追其根源都是画出来的,但通过查看源码可以发现,Flutter 中绝大多数组件并不是使用 CustomPaint 组件来画的,其实 CustomPaint 组件是对框架底层绘制的一层封装。这个系列便是对 Flutter 绘制的探索,通过测试调试源码分析来给出一些在绘制时被忽略从未知晓的东西,而有些要点如果被忽略,就很可能出现问题。

Flutter 绘制探索 1 | CustomPainter 正确刷新姿势


2.本文测试案例

Flutter 框架中的三位主角团 ElementRenderObjectWidget 是最顶层的抽象。它们三者之间存在着什么关系呢?CustomPainter 在这三位的光环下,又扮演这什么样的角色呢?本文将通过一个精简的绘制案例,来稍稍揭开一点 Flutter 框架运转的秘密。如下代码,直接使用 CustomPaint 组件作为 runApp 的入参。

void main() => runApp(CustomPaint(
      painter: ShapePainter(color: Colors.blue),
    ));

class ShapePainter extends CustomPainter {
  final Color color;
  ShapePainter({this.color});
  
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()..color = color;
    canvas.drawCircle(Offset(100, 100), 50, paint);
  }
  
  @override
  bool shouldRepaint(covariant ShapePainter oldDelegate) {
    return oldDelegate.color != color;
  }
}
复制代码

一、CustomPainter 相关对象

1. CustomPaint 类简介

现在台面上只有 CustomPaintCustomPainter 两个类,下面是它们的继承关系。


CustomPaint 是一个 Widget 的子类,我们知道 Widget 是一个功能很少的抽象类,主要用于 Element 的创建。


但衍生到 RenderObjectWidget 时,增加了抽象方法 createRenderObject 来创建 RenderObject。 所以这一族的 Widget 承担着创建 RenderObject 的使命。 RenderObjectWidget 本身也是抽象类,所以这两个抽象方法将由它的子类实现。


SingleChildRenderObjectWidget 也是抽象类,继承自 RenderObjectWidget,它自身通过 SingleChildRenderObjectElement 完成了 createElement 方法。所以其子类的任务就是实现创建 RenderObject 的那个抽象方法 createRenderObject


就这样,使命的 星火相承,在 CustomPaint 中对 createRenderObject 进行实现。返回的是 RenderCustomPaint ,这样 RenderCustomPaint 便登上了台面 。 CustomPaint 作为一个 Widget 的实现类,拥有 5 个 final 的成员属性,这些属性会在构造方法中进行初始化。


2.RenderCustomPaint 类简介

现在 RenderCustomPaint 出场,简图如下:


RenderCustomPaintRenderObject 的子类。RenderObject 是一个非常复杂的类,本文并不打算详细说它,后面有机会单独写一篇。这里简单地认识一下它:首先它是一个树状结构,通过 adoptChilddropChild 来维持渲染树的节点。其次,该类型对象需要负责布局 layout 和 绘制 paint ,通过 markNeedsPaint 方法可以标记当前 RenderObject 需要被绘制。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  
  Constraints? _constraints;
  
  void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
  void paint(PaintingContext context, Offset offset) { }
  
  @override
  void adoptChild(RenderObject child) { ... }
  
  @override
  void dropChild(RenderObject child) { ...  }
  
  void markNeedsPaint() {}
}
复制代码

RenderBox 是目前 Flutter 框架中 RenderObject 的直系子类。它是 二维笛卡尔坐标系中的渲染对象,在 RenderObject 中只定义了约束 BoxConstraints ,在 RenderBox 中进一步定义了尺寸 Size 。一个 Size 对应一个布局范围的 box ,在这个范围内就是一个左上角为 0,0 的笛卡尔坐标系。


RenderCustomPaint 是最终的实现类,它实例化的时机是 CustomPaint 组件调用 createRenderObject 时。其中构造函数的入参也就是 CustomPaint 的那几个成员,也就是说,我们自定义的 CustomPainter 画板最终会交给这个类进行使用。

目前这几个类之间的关系简图如下:


3.CustomPainter 类简介

CustomPainter 是一个抽象类,它继承自 Listenable ,内部还持有一个 Listenable 类型的成员 _repaint ,它有两个抽象方法 paintshouldRepaint 。 所以想使用 CustomPainter 就需要继承它,并实现其抽象方法。它被 CustomPaint 持有,并传递到 RenderCustomPaint 类中。

上一篇 CustomPainter 正确刷新姿势 中说到,可以通过 repaint 设置可监听对象来触发画板刷新。从这里就可以看到其中的原理。在 RenderCustomPaint#attach 方法中 _painter 会添加监听执行 markNeedsPaint 方法,从而触发重绘。而 CustomPainter#addListener 就是在监听 _repaint 成员的变化,触发回调。

---->[RenderCustomPaint#attach]----
@override
void attach(PipelineOwner owner) {
  super.attach(owner);
  _painter?.addListener(markNeedsPaint);
  _foregroundPainter?.addListener(markNeedsPaint);
}

---->[CustomPainter#addListener]----
@override
void addListener(VoidCallback listener) => _repaint?.addListener(listener);
复制代码

二、 RenderObjectElement 登场

这时会有一个问题:CustomPaint#createRenderObject 是在何时被触发的?遇事不决,调试一下。可以看出是在 RenderObjectElement.mount 方法中被触发的。


可以看一下当前的 this 对象,是 SingleChildRenderObjectElement 类型,可见它持有一个 _widget 成员,正是之前传入的 CustomPaint 对象。通过widget.createRenderObject 为成员 _renderObject 赋值。另外入参是 this,也说明入参的 BuildContext 对象正是当前的 SingleChildRenderObjectElement


SingleChildRenderObjectElement 浮上台面,这张简图便丰富了。

Element 是元素抽象的顶层,本身持有 Widget 对象,会衍生出两大种类:ComponentElementRenderObjectElement。其中只有 RenderObjectElement 才持有 RenderObject。也就是说,对于RenderObjectElement 而言,会同时持有 WidgetRenderObject

总结一下,RenderObjectElementRenderObject 成员会在执行 mount 方法执行时进行赋值,其值为使RenderObjectWidget#createRenderObject 返回值。到这里便可以总结一下这几个类直接的关系了。


三、相关各类之间的关系

1、RenderObjectWidgetRenderObject 的关系

RenderObjectWidget 作为一个 Widget 的衍生类,拥有创建 RenderObject 对象的使命。其子类必须实现 createRenderObject 抽象方法。两者之间的关系是 创造者-RenderObjectWidget被创造者-RenderObject
例如:对于 CustomPaint 组件来说,createRenderObject 方法负责创建 RenderCustomPaint 这个 RenderObject 对象。

---->[CustomPaint#createRenderObject]----
@override
RenderCustomPaint createRenderObject(BuildContext context) {
  return RenderCustomPaint(
    painter: painter,
    foregroundPainter: foregroundPainter,
    preferredSize: size,
    isComplex: isComplex,
    willChange: willChange,
  );
}
复制代码

2. RenderObjectElementRenderObject 的关系

RenderObjectElement 作为一个 Element 的衍生类,类中会同时持有 WidgetRenderObject 成员。两者之间的关系是 使用者-RenderObjectElement被使用者-RenderObject 。且在 RenderObjectElement#mount 方法中, RenderObject 成员被初始化。
例如:对于 SingleChildRenderObjectElement 对象来说,RenderObject 类型的 _renderObject 是该对象的一个成员。

---->[RenderObjectElement 源码]----
abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  @override
  RenderObject get renderObject => _renderObject; 
  RenderObject _renderObject;
  @override
  
  void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this); // RenderObject 成员初始化
  // ...
}
复制代码

3.WidgetElement 的关系

从 Widget 类的那短短几行源码也可以看出:Widget 最重要的目的是创建 Element

每个 Element 都会持有一个 Widget 对象,并且这个 Widget 对象创造了该元素。

另外, Widget#createElement 在整个 Flutter 框架中只出现过两次,其一是根元素节点创建时。

其二就是的 Element#inflateWidget,可以说这里是 Element 的孵化室,除了根元素节点外,所以的 Element 都是在这个方法里实例化的。这个主题有机会再细说。

现在应该对整体有了一个简单地把握,通过下面的图片可以让你更形象地认识它们之间的关系。


4.CustomPainter 相关类小结

我们可以看出,和 CustomPainter 相关的类有 CustomPaint 这个 Widget,以及 RenderCustomPaint这个 RenderObject 对象。 CustomPaint 只是作为一个载体,将画板对象传给 RenderCustomPaint 。所以和CustomPainter 关系最密切的是RenderCustomPaint 类。在该类中会对 CustomPainter 中的可监听对象进行监听,触发 RenderCustomPaint 对象的重绘,另外 CustomPainter 的绘制操作也是在该类中进行回调的,这里也是 CustomPainter#paint 中 Canvas 对象的来源之处。

本文简单梳理了一下和绘制相关的几个类之间的关系,下一篇将进一步探索 Flutter 绘制相关的源码,全面分析CustomPainter 这个类,着重看一下 paintshouldRepaint 两个抽象方法触发的时机。


@张风捷特烈 2021.01.12 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~

文章分类
Android
文章标签