什么是Widgets,Elements和RenderObjects

有没有想过 Flutter 如何获取这些小部件并将其实际转换为屏幕上的像素?

理解基础技术的工资原理将使优秀的开发人员与开发人员脱颖而出。

当你知道有效的方法和无效的方法时,可以更轻松地创建自定义布局和特殊效果;并且知道这些将节省你在键盘上待几个晚上的时间。

这篇文章的目的是向你介绍 Flutter 之外的世界。我们将研究 Flutter 的不同方面,并了解其实际工作原理。

让我们开始吧

你可能已经知道如何使用 StatelessWidgetStatefulWidget。但是这些小部件仅仅构成其他小部件,布置小部件并渲染他们发生在其他位置。

打开你喜欢的 IDE,然后再进行操作,查看实际代码中的结构通常会造成这些『aha』时刻。

不透明度

为了熟悉 Flutter 原理的基本概念,我们将看一下 Opacity 小部件并进行检查。由于它是一个非常基本的小部件,因此是一个很好的例子。

它只接受一个 child 参数,因此,你可以将任何 Widget 包裹在 Opacity 中,并更改其显示方式。除了 child,它还接受一个名为 opacity 的参数,其值为 0.0-1.0 之间,用于控制不透明度。

Opacity 小部件

Opacity 是继承 SingleChildRenderObjectWidget

类的继承关系如下:

Opacity -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget

相反的,StatelessWidget 和 StatefulWidget 如下

StatelessWidget/StatefulWidget -> Widget

区别在于,StatelessWidget/StatefulWidget仅仅构成小部件,而 Opacity 部件实际上会更改部件的绘制方式。

但是,如果你查看这些类中的任何一个,将找不到实际绘制不透明度相关的任何代码。

这是因为部件仅仅保存表面配置信息。

在这个列子中,Opacity 部件仅仅保存了不透明度的值。

这就是为什么每次调用 build 函数时都可以创建新的小部件的原因。因为小部件的构造并不昂贵,它们仅仅是信息的容器。

渲染 - Rendering

但是渲染是在哪里发生?

它在 RenderObjects 内部

正如你可能从单词中猜到的那样,RenderObject 是负责一些事情,包括渲染

Opacity 小部件创建了一个 RenderObject 并通过这些方法去更新

  @override
  RenderOpacity createRenderObject(BuildContext context) {
    return RenderOpacity(
      opacity: opacity,
      alwaysIncludeSemantics: alwaysIncludeSemantics,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
    renderObject
      ..opacity = opacity
      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
  }

源码

RenderOpacity

Opacity 部件的大小与其子部件 child 的大小完全相同。

它基本上模仿了 child 的各个方面,除了绘制。在绘制其子部件之前,会先为其添加不透明度。

在这种情况下,RenderOpacity需要去实现所有方法(例如执行布局、命中测试、计算大小),并要求其子部件执行实际工作

RenderOpacity继承RenderProxyBoxRenderProxyBox混合在其他几个类中,这些类恰好实现了这些方法,并将实际计算推迟到了唯一的子部件中)

double get opacity => _opacity;
double _opacity;
set opacity(double value) {
  _opacity = value;
  markNeedsPaint();
}

删除了一些 assert 断言和优化判断。源码

字段通常将 getter 暴露给私有变量,而 setter 除了设置字段外,还调用 markNeedsPaint()markNeedsLayout()。顾名思义,它在告诉系统『我发生了改变,请重新绘制/重新布局』

RenderOpacity 中,可以找到以下方法:

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      if (_alpha == 0) {
        // No need to keep the layer. We'll create a new one if necessary.
        layer = null;
        return;
      }
      if (_alpha == 255) {
        // No need to keep the layer. We'll create a new one if necessary.
        layer = null;
        context.paintChild(child, offset);
        return;
      }
      assert(needsCompositing);
      layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer);
    }
  }

PaintingContext 基本上是一块精美的画布,在这个画布上,有一个方法 pushOpacity,这才是实际的不透明度实现。

现在来回顾一下

  • Opacity小部件不是一个 StatelessWidgetStatefulWidget,而是 SingleChildRenderObjectWidget
  • Widget仅能保存渲染器可以使用的信息
  • 在本例中,Opacity 布局持有一个代表不透明度的 double 类型的值
  • RenderOpacity 继承于 RenderProxyBox进行实际的 layouting/rendering 等操作
  • 因为不透明度的行为几乎与它的子部件完全一样,所以她将美国方法调用委托给了子部件
  • 重写 paint 方法并调用了 pushOpacity,后者将所需的不透明度添加到小部件中

当然,还不止这些

首先,我们知道了Widget只是一个配置,RenderObject只管理布局和渲染等。

Flutter 中,基本上一直在重新创建小部件。调用 build() 方法时,会创建一堆小部件。每次发生变化的时候都会调用这个 build() 方法。例如,当动画发生时,会经常调用 build() 方法。

这意味着你不能每次都重建整个子树,相反,你想要更新它。

你不能在屏幕上获得小部件的大小或位置,因为小部件就像蓝图,它实际上并不在屏幕上。

它只是对底层呈现对象应该使用哪些变量的描述。

引入 Element

Element 是整个树中的一个具体的小部件。

基本上是这样的:

第一次创建小部件的时候,它会在内部创建一个 Element。然后将这个 Element 插入到树中。如果 widget 稍后发生更改,则将它与旧的 widget 进行对比,并相应地更新 element

最重要的是,Element不会被重新构建,它只会被更新。

Element 是核心框架的中心部分,显然还有更多内容,但是现在这些信息以及足够了。

在这个Opacity 小部件中的Element又是在哪里创建的呢?

对于那些充满好奇心的人来说,这只是一小段插曲

SingleChildRenderObjectWidget 中,可以找到下面这样的代码:

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  
  const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
  
  final Widget child;

  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

如我们所见,SingleChildRenderObjectElement 是在 SingleChildRenderObjectWidget 中被创建的,这里的 this,即SingleChildRenderObjectWidget,所以SingleChildRenderObjectElement中的widget即当前的传入的 this

类的继承关系如下:

SingleChildRenderObjectElement -> RenderObjectElement -> Element

Element 会创建 RenderObject,但是在我们的示例中,Opacity 小部件为什么是自己创建的 RenderObject

  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

通常更常见的情况是,Widget 需要一个 RenderObject,但不需要定制 Element

RenderObject 实际上是由 Element 创建的。

SingleChildRenderObjectElement 获得对象 RenderObjectWidget (RenderObjectWidget 有创建 RenderObject的方法)。

mount 方法中,将 Element 插入到 Element 树中,这些都是在 RenderObjectElement 中发生的

@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);

    _renderObject = widget.createRenderObject(this);
    
    attachRenderObject(newSlot);
    _dirty = false;
  }

紧接在 super.mount(parent, newSlot) 之后。

只有一次(当它被挂载时)它会问小部件:请给我你想要使用的renderobject,这样我可以保存它。

最后

这就是 Opacity 小部件内部工作的方式。

这篇文章的目的是向你介绍 widget 之外的世界。仍然有许多主题要去涉及,但希望能给你一个关于内部工作原理的很好的介绍。