(翻译)什么是 Widget, RenderObject 和 E
你是否有过这样的疑问: Flutter 如何处理 Widget
并把它们转换成屏幕上的像素呢?
没有吗? 但是你应该有的!
理解底层技术运作原理, 可以决定你是一个好的开发者还是一个伟大的开发者.
当你知道什么东西凑效和什么东西不凑效的时候, 你可以更简单地创建自定义布局和特殊动画; 而知道这个可以让你少熬夜敲键盘;
这篇文章的目的是像你介绍隐藏在 Flutter 表面之下的世界. 我们会探索 Flutter 的不同方面, 并且理解它是如何工作的.
开始吧
你大慨已经知道怎么使用 StatelessWidget
和 StatefulWidget
. 但是它们只是组装了其他组件, 布局和渲染发生在其他地方.
我详列建议你打开自己最喜欢的 IDE 然后跟着下面去做, 亲眼查看代码结构总会让人很惊讶. 在 Intellij 中你可以双击 shift 并且输入一个类的名字去找到这个类的代码
Opacity
为了熟悉 Flutter 运作的基础概念, 我们先看看 Opacity
组件并且研究一下它. 因为它是一个非常基础的组件, 是一个很好的参照例子.
它只接收1个子组件, 因此你可以在 Opacity
里面包裹任何组件并且控制它的显示方式. 除了子组件, 就仅有1个 opacity
的 double
型参数, 限制范围介于0.0到1.0. 它用于控制不透明度.
Widget
Opacity
是一个 SingleChildRenderObjectWidget
.
其继承层级如下所示:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
相对地, StatelessWidget
和 StatefulWidget
的如下所示:
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
以外的世界. 还有很多内容需要讨论, 但是我希望这里已经给了各位较好地介绍了内部运作原理.