(翻译)什么是 Widget, RenderObject 和 E

2020-01-07  本文已影响0人  奥利奥_2aff

原文: Flutter, what are Widgets, RenderObjects and Elements?

你是否有过这样的疑问: Flutter 如何处理 Widget 并把它们转换成屏幕上的像素呢?

没有吗? 但是你应该有的!

理解底层技术运作原理, 可以决定你是一个好的开发者还是一个伟大的开发者.

当你知道什么东西凑效和什么东西不凑效的时候, 你可以更简单地创建自定义布局和特殊动画; 而知道这个可以让你少熬夜敲键盘;

这篇文章的目的是像你介绍隐藏在 Flutter 表面之下的世界. 我们会探索 Flutter 的不同方面, 并且理解它是如何工作的.


开始吧

你大慨已经知道怎么使用 StatelessWidgetStatefulWidget. 但是它们只是组装了其他组件, 布局和渲染发生在其他地方.

我详列建议你打开自己最喜欢的 IDE 然后跟着下面去做, 亲眼查看代码结构总会让人很惊讶. 在 Intellij 中你可以双击 shift 并且输入一个类的名字去找到这个类的代码

Opacity

为了熟悉 Flutter 运作的基础概念, 我们先看看 Opacity 组件并且研究一下它. 因为它是一个非常基础的组件, 是一个很好的参照例子.

它只接收1个子组件, 因此你可以在 Opacity 里面包裹任何组件并且控制它的显示方式. 除了子组件, 就仅有1个 opacitydouble型参数, 限制范围介于0.0到1.0. 它用于控制不透明度.

Widget

Opacity 是一个 SingleChildRenderObjectWidget.

其继承层级如下所示:

Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget

相对地, StatelessWidgetStatefulWidget 的如下所示:

StatelessWidget/StatefulWidget → Widget


不同之处在于, Stateless / StatefulWidget 只会组装组件, 而 Opacity 会改变组件的渲染方式.

但是如果你再查看这些类, 你也找不到和绘制不透明度相关的代码.

因为一个组件只会存储配置信息. 在这个场景下, Opacity 组件只是保存了不透明度的值.

这就是为什么每次 build 函数调用的时候, 你都可以创建新的组件. 因为组件的构建是低开销的. 它们只不过是信息的容器.

渲染

但是, 渲染到底在哪里发生?

RenderObject

正如名字透露的一样, RenderObject 负责一部分职责, 包括了渲染.

Opacity组件通过下列函数创建并且更新 RenderObject.

@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);

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

源码

RenderOpacity

Opacity 组件的尺寸和它的子元素一致. 除了绘制以外, 它各方面和子元素一样. 它在绘制子元素之前对其增加了一个不透明度.

这个情况下, RenderOpacity 需要实现所有函数(例如: performing layout/hit testing/computing sizes)并且让子元素进行真正的对应工作.

RenderOpacity 继承了 RenderProxyBox (混合了其他一些类). 它实现了那些函数并且延迟了子元素的实际计算时机.


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

这里移除了较多代码中的断言/优化. 查看完整源码

代码中经常会实现 getter 函数去公开私有属性. 同时也实现了 setter 函数, 而且会在 setter 内部调用 markNeedsPaint() 或者 markNeedsLayout(), 用于告诉系统组件自身发生变动了, 是时候重新绘制/布局了.


RenderOpacity 里我们会看见这个函数:

@override
void paint(PaintingContext context, Offset offset) {
    context.pushOpacity(offset, _alpha, super.paint);
}

同样移除了较多代码中的断言/优化. 查看完整源码

PaintingContext 基本上是一个神奇的画布. 它有一个函数pushOpacity. 就是它了.

这一行就是不透明度实现的本质.

回顾一下

所以就是这些了吗? 一部分吧.

我们要谨记, Widget 本质上只是一个配置, RenderObject 负责布局/渲染等工作.

Flutter 里面我们经常重新创建 Widget. 当 build 函数调起的时候我们创建一堆 Widget. 这个函数在某些东西发生变动的时候就会被调起. 比如当一个动画要执行, build 函数就会频繁地被调起. 这意味着我们不能每次都重构整个树. 但是我们需要更新它.

我们无法得知一个 Widget 在屏幕上的位置, 因为 Widget 就像是蓝图一样, 它不实际存在于屏幕上. 它只是描述了它包含的内容的参数.

介绍一下 Element

Element 是树中的一个具体部件.

基本上事实上是这样的:

当一个 Widget 被创建出来之后, 它就会被标记为一个 Element. 这个Element 会被插入到树中. 如果 Widget 发生了变动, 那么它会被和旧的 Widget 对比, 然后 Element 会相应地更新. 这里的重点是, Element 不会被重新构造, 它只是被更新了.

各个Element 是核心框架的中心, 显然关于它们还有更多东西. 但是目前我们了解到这里就足够了.

在不透明度例子里面, Element 是在什么时候创建的?

给那些老顽固们一小段代码.

SingleChildRenderObjectWidget 创建了它.

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

源码

SingleChildRenderObjectElement 是拥有1个子元素的 Element.

Element 会自己创建 RenderObject, 但是为什么在 Opacity 中它自己创建了 RenderObject.

这只是为了 API 的平滑. 因为大多数情况下 Widget 需要 RenderObject 而不是自定义的 Element. RenderObject 实际上是被 Element 创建的. 我们看下面代码:

SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

源码

SingleChildRenderObjectElement 拿到 RenderObjectWidget 的引用(它有创建RenderObject的函数).

RenderObjectElement中, 函数mount就是Element被插入树中的地方. 在这里, 神奇的事情发生了.

@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

super.mount(parent,newSlot做的事情, 源码

这里从 Widget 获取了 RenderObject并且保存起来了, 这个操作只会执行一次(mount函数执行的时候).

结语

这些就是 Opacity 组件内部的工作.

这篇文章的目的是为了介绍 Widget 以外的世界. 还有很多内容需要讨论, 但是我希望这里已经给了各位较好地介绍了内部运作原理.

上一篇下一篇

猜你喜欢

热点阅读