【译】Flutter,什么是Widgets、RenderObjects、Elements?

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

你可曾想过 Flutter 是如何处理 Widgets 并将他们转换成像素显示在屏幕上的?还没有?

你应该思考一下。

是否理解系统的底层实现原理是区分一个优秀程序员的关键。

当你了解什么最有效的时候,你才能更轻松地创建布局和特效,从而节省大量的时间。

这篇文章的目的是向你介绍 Flutter 内部的工作原理,我们将从不同的角度来看 Flutter,理解它到底是如何工作的。

让我们开始吧

你可能已经知道如何使用 StatelessWidgetStatefulWidget 了,但是它们只是用来组装控件的容器,布局和绘制的工作是在其他地方完成的。

我强烈建议你打开自己喜欢的 IDE 并继续阅读,只有看着实际的代码才能让你感到“噢,原来是这样的”。在 Intellij 中,你可以通过双击 shift 并输入类名来查找相应代码。

Opacity

为了熟悉 Flutter 工作的基本原理,我们先来看一个最基础的控件 Opacity,它将是一个很好的例子。

Opacity 接收一个 child,所以你可以用 Opacity 来包装任意的 Widget 从而改变它的外观。另外,它还接收一个名为 opacity 的属性,用来设置控件的不透明度,取值在 0.0 到 1.0 之间。

Widget

Opacity 是一个 SingleChildRenderObjectWidget

这个类的继承关系如下:

Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget

相应的,StatelessWidgetStatefulWidget 的继承关系如下:

StatelessWidget / StatefulWidget→Widget

它们的不同之处在于,Stateless / StatefulWidget 只是将其他 Widget 组装起来,而 Opacity 会真正地影响 Widget 的绘制。

但是如果你去那些代码中找的话,你是不可能找到任何与属性 opacity 相关的绘制代码。

那是因为 Widget 仅仅只持有控件的配置信息。比如这个例子中,控件 Opacity 只是用来持有属性 opacity 的。

这也就是你每次都可以在 build() 函数中新建 widget 的原因。构建 widget 的过程并不耗费资源,因为 Wiget 只是用来保存属性的容器

Rendering

那么渲染是在哪完成的呢?

答案是 RenderingObject

正如你能从名字中猜出的那样,RenderingObject 负责渲染相关的工作。

Opacity 通过下面这些方法来创建和更新 RenderingObject:

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

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

源码

RenderOpacity

除了绘制,Opacity 和它的 child 几乎一模一样,它用 child 的大小作为自身大小。在绘制它的 child 之前,它给 child 增加了一个不透明度。

所以,RenderOpacity 需要实现包括布局、点击检测、计算大小在内的所有的函数,并将其转交给它的 child 来完成(也就相当于一个 child 的代理)。

RenderOpacity 继承于 RenderProxyBoxRenderProxyBox 中实现了向 child 的工作交接。

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

完整的源码

在 setter 方法中除了设置字段的值外,还调用了 markNeedsPaint() (或者 markNeedsLayout()),顾名思义,它告诉系统“我已经发生了改变,请重新进行绘制(或布局)”。

RenderOpacity 中,我们找到了下面这个方法:

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

完整的源码

PaintingContext 就是进行绘制操作的画布,这里通过在 canvas 上调用名为pushOpacity的方法来实现不透明度的控制。

回顾一下

  • Opacity 不是 StatelessWidget 或者 StatefulWidget,而是 SingleChildRenderObjectWidget
  • Widget 仅用于存储渲染所需要的信息;
  • 在这里,Opacity 存储了一个双精度值的不透明度;
  • 布局和渲染等操作实际是由继承至 RenderProxyBoxRenderOpacity 完成的;
  • 因为 Opacity 并不改变 child 的其他行为,所以它的每个方法都仅仅只是 child 的代理;
  • 通过重载 paint 方法并调用 pushOpacity,RenderOpacity 实现了向 Widget 添加不透明度的需求。

That’s it? Kind of.

记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。

在 Flutter 中,你基本上一直都在不停的创建 Widgets,当 build() 方法被调用时,你创建了一堆 Widgets。

每当有什么变化产生的时候,build() 方法都会被调用。例如播放一个动画,build() 方法就会被频繁调用。这意味着你不能总是重新构建一整颗渲染树,相反,你应该做的知识去更新这颗树。

你无法获取一个 widget 在屏幕上的位置和大小,因为 widget 就像一张蓝图,它并非真实地显示在屏幕之上,它只描述了底层渲染对象应该具有的那些属性。

Element

Element 是这颗巨大的控件树上的实体。

基本上会发生什么:

在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 被不会重建,只是更新而已。

Elements 是 Flutter 核心框架的重要组成部分,显然它并不仅仅如此,但目前对我们来说,知道这些就足够了。

在 Opacity 示例中,element 是在哪创建的?

SingleChildRenderObjectWidget 中创建了它:

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

源码

SingleChildRenderObjectElement 则是一个仅拥有一个 child 的元素。

Element 创建 RenderObject,但在示例中是 Widget 创建的 RenderObject?

这仅仅是为了平滑的 API,因为常见的情况是 Widget 需要一个 RenderObject 而不是自定义 ElementRenderObject 实际是由 Element 来创建的,让我们来看看。

SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

源码

在构造函数中,SingleChildRenderObjectElement 拿到了一个 RenderObjectWidget 的引用(其中包含了创建 RenderObject 的方法)。

Element 通过 mount 方法插入到 Element Tree 中,这里就是 Element 创建 RenderObject 的地方:

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

源码

一旦 Element 被挂载到树上,它便会向 Widget 请求 “请给我你要使用的 RenderObject,这样我就能保存它了”。

结语

这就是 Opacity 控件内部的工作方式。
这篇文章的目标是向你介绍 widget 之外的世界。这里任然还有很多话题要讨论,但我希望你已经很好地理解了其内部的工作原理。

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

推荐阅读更多精彩内容

  • 本文主要介绍了Flutter布局相关的内容,对相关知识点进行了梳理,并从实际例子触发,进一步讲解该如何去进行布局。...
    Q吹个大气球Q阅读 9,590评论 6 51
  • 译者丨覃云 Flutter 是什么? Flutter 移动应用程序 SDK 是为开发人员提供一种创建快捷、美观的应...
    言射手阅读 7,755评论 1 14
  • 1️⃣Flutter中的UIView相当于什么? 在iOS中,您在UI中创建的大部分内容都是使用视图对象完成的,这...
    盖世英雄_ix4n04阅读 1,131评论 0 1
  • 记得那节语文课上,徐老师走进教室,给我们带来了三个杯子。一个杯子里装满了水,一个杯子里装满了沙子,还有一个...
    郭家豪阅读 276评论 0 2
  • 文|良大师 1. 近日,网友大东联系我,要我解答他的疑惑。 他是部门的顶梁柱,技术玩得溜,人也处得好,领导很赏识。...
    良大师阅读 2,456评论 16 63