Widget、Elment和RenderObject
本文也发布于本人的知乎专栏:https://zhuanlan.zhihu.com/p/394568668
引子
在Flutter源码阅读分析:Framework层的启动中,我们分析了Framework层的启动流程,其中讲到了在runApp
方法中,调用到了attchRootWidget
方法:
// ./packages/flutter/lib/src/widgets/binding.dart
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
这个方法获取一个Widget
并将其附到renderViewElement
上,在必要的时候创建这个renderViewElement
。
其中涉及到了Widget
、Element
和Render
,都属于Flutter渲染机制。本文将对Flutter渲染机制进行分析。
首先看一下RenderObjectToWidgetAdapter
这个类和其构造方法:
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
this.container,
this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
...
}
这个类的作用是桥接RenderObject
和Element
树,其中container
就是RenderObject
,而Element
树则插入在其中。类型参数T
是一种RenderObject
,是container
期望其孩子的类型。
再看一下RenderObjectToWidgetAdapter
类的attachToRenderTree
方法:
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
// This is most likely the first time the framework is ready to produce
// a frame. Ensure that we are asked for one.
SchedulerBinding.instance.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
这个方法填充了widget
,并且将结果RenderObject
设置为container
的孩子。如果element
为空,那么则会创建一个新的element
,否则的话,给定的element
会调度一个更新,使得与当前widget
关联。
从上面可以看出,对于Flutter框架来说,主要关注的就是Widget
、Element
、RenderObject
。下面我们来分析一下这三者的特点和关系。
Widget
在上文中的例子中,rootWidget
是用户开发的Flutter应用的根节点,是一个Widget
类。
在Widget
类的注释中,官方给出的定位是用于描述Element
的配置。Widget
是Flutter框架的中心类结构。一个Widget
是UI中一个固定不变的部分。可以被填充成Element
,而Element
又管理底层的渲染树。
Widget
本身是没有可变状态的,所有的成员变量都是final
的。如果需要和一个Widget
关联的可变状态,可以使用StatefulWidget
,这个类会创建一个StatefulWidget
,而它又会在填充成element
和合并到树中的时候创建一个State
对象。
一个给定的Widget
可以被包含到树中0次或更多次。特别是Widget
可以被多次放置到树中。每次Widget
被放置到树中,都会填充成一个Element
。看一下Widget
基类的方法声明:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({
this.key });
final Key key;
@protected
Element createElement();
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
...
}
static int _debugConcreteSubtype(Widget widget) {
...
}
}
分别介绍一下这几个方法和成员变量。
首先是key
这个成员变量,它用于控制在树中一个Widget
如何替换另一个。主要有以下几种方式:更新Element
、替换Element
以及换位置。通常情况下,如果一个Widget
是另一个的唯一孩子,那么不需要明确的key
。
createElement
方法用于将配置填充为一个具体的实例。
canUpdate
方法用于判断newWidget
能否用于更新当前以oldWidget
为配置的Element
。
_debugConcreteSubtype
方法返回一个编码值,用于指示Widget
的实际子类型,1表示StatefulWidget
,2表示StatelessWidget
。
StatefullWidget
和StatelessWidget
都是Widget
的抽象子类,下面看一下这两个子类的具体情况。