(翻译)什么是 Widget, RenderObject 和 Element?

原文: 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. 就是它了.

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

回顾一下

  • Opacity 既不是 StatelessWidget 也不是StatefulWidget, 而是一个 SingleChildRenderObjectWidget.
  • Widget 只负责存储信息.
  • Opacity 存储了不透明度对应的 double 值.
  • RenderOpacity 继承了 RenderProxyBox, 它做了实际的布局和渲染工作.
  • 因为不透明度和其子元素是几乎一致的, 所以它负责代理调用子元素的函数.
  • RenderOpacity 重写了函数 paint, 并且在内部调用了 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 以外的世界. 还有很多内容需要讨论, 但是我希望这里已经给了各位较好地介绍了内部运作原理.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容