开篇
Flutter中页面的渲染渲染离不开三个重要的元素:Widget、Element、RenderObject。是一个从Widget到Element再到RenderObject的过程。而具体到源码中,这个转换的工作时如何实现的呢,今天我们就来跟着源码简略分析一下。为了更流程的分析它们三者之间的关系,我们先根据Framework层源码的注释了解一下它们,然后再从Flutter项目入口进行分析。
Widget、Element和RenderObject
根据源码注释我么可以大致知道:
-
Widget
是对UI的一种廉价的、不可变的,描述(如何配置子树)Element
的配置,并可以生成Element
-
Widget
本身没有状态,所有属性都是final,Element
持有Widget
和RenderObjec
t的实例 - 给定
Widget
可不断加入到树中不同的位置,并生成Element
作为它在树中的实例,它们是一对多的关系 -
RenderObject
是实际的渲染对象,Element
就可以理解为将变化的Widget
转化为较为稳定的RenderObject
Flutter工程的入口
新建一个Flutter项目后,我们会看到其工程目录如下
其中,
main.dart
文件中有main
函数作为程序的入口:
void main() {
runApp(MyApp());
}
其中MyApp()
就是一个Widget,我们忽略其他代码,只关心它在代码中的走向,发现它最终作为一个child
用于RenderObjectToWidgetAdapter
的初始化,并且通过attachToRenderTree
方法生成了一个Element
。
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
继续看它的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;
}
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
可以发现:在我们创建的“根”Widget
之前,会有一个RenderObjectToWidgetAdapter
作为真正的根Widget
,而与之对应的是一个RenderObjectToWidgetElement
作为根Element
。通过调用createElement
方法初始化与之对应的Element
。该方法在抽象类Widget
中被定义,可被子类重写。
而观察Element的初始化方式以及定义:
Element(Widget widget)
: assert(widget != null),
_widget = widget;
不难发现:Element
初始化时接受了当前的Widget
并作为私有变量持有。那么我们的Widget,MyApp
是在何时转换成Element
的呢?
自上而下的调用创建Element
继续追踪attachToRenderTree
方法源码,我们发现在Element
被初始化后,会调用element.mount(null, null)
方法(只保留关键代码),该代码在framework.dart
文件中的Element
类中。
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
void _rebuild() {
try {
//_child是其子Element,也就是`MyApp`将要生成的Element,此时进行初始化
//widget.child是我们的`MyApp`
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
...
}
}
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
//如果newWidget为空,说明没有子树,返回null
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
//此时_child为null
if (child != null) {
...
} else {
//初始化
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild;
}
}
//调用`MyApp`的`createElement`方法进行初始化
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
//执行monut方法,继续下一个Widget的转换
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}
可以发现,当我们的程序启动时,runApp
会创建一个根Widget并调用其createElement
方法生成element。并执行element的mount
方法。之后,element会读取并判断自己所持有的Widget对象是否有子Widget,就是上面代码中widget.child
。当发现child不为空,就会继续执行createElement()
方法,并执行mount()
方法进入下一次子树。依次类推直至遍历完所有子树。
依旧是自上而下的调用却有点不一样
在追踪上述代码时,我们并没有直接发现类似createElement
的createRenderObject()
方法被调用,我们观察RenderObjectToWidgetElement
的继承关系:
最终可在
RenderObjectElement
的mount()
方法里看到这样的代码:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
....
_renderObject = widget.createRenderObject(this);
....
}
在这里,我们发现了初始化RenderObject
的地方。可以发现,RenderObjectWidget
实在Element里被初始化,并且和Widget一样被Element所持有。并且,采取和Element一样,自上而下的不断遍历整个树进行初始化。
但是有一点我们需要注意:
createElement()
方法在所有widget的基类Widget
中被定义了。但是createRenderObject()
方法是在Widget
的子类RenderObjectWidget
中被定义的,于此同时,createRenderObject()
方法也只会在RenderObjectElement
中被调用。
这说明:每个Widget都会生成一个Element,但并不是每个Element都会创建一个RenderObject,只有RenderObjectWidget类型的子类的Widget
创建的继承自RenderObjectElement
类型的element才会创建RenderObject。也就是说,widget树、element树和RenderObject树并不是一是一对应的。
总结
本文只是Flutter UI相关的冰山一角,并没有深入了解Flutter的绘制原理。旨在通过源码了解Widget、Element和RenderObject直接的关系,理清他们之间的逻辑。