在前面我们介绍各种各样的Widget,相信大家对Wiget的使用都已经有了自己的认识,今天我们就从底层角度看下Widget是如何工作,是什么支撑起了Wiget这个系统。
其实,Widget并不是我们真正看到的视图,背后究竟是什么?其实Flutter Framework提供了三种视图树,即:Widget 、Element、 RenderObject,只不过,我们使用Flutter开发界面时,通常只和widget打交道。
Widget是用户界面的一部分,并且是不可变的(immutable)。Widget会被inflate到Element,并由Element管理底层渲染树。
Widget本身没有可变状态(所有的字段必须是final)。如果想要把可变状态与Widget关联起来,可以使用StatefulWidget,StatefulWidget通过使用StatefulWidget.createState方法创建State对象,并将之扩充到Element以及合并到树中。
Widget通过createElement方法使得子类创建Element
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
///Inflates this configuration to a concrete instance.
@protected
Element createElement();
Element 是 Widget 的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁,它是我们最终看到的对象。
Element也可以理解为,Widget中额外的属性,可以用来存储Widget的状态和额外的值。
再来看看文档中对于Element的介绍:
An instantiation of a Widget at a particular location in the tree.
Element是在树中特定位置Widget的实例;
Flutter 渲染过程,可以分为这么三步:
可以看到,Element 同时持有 Widget 和 RenderObject。而无论是 Widget 还是 Element,其实都不负责最后的渲染,只负责发号施令,真正去干活儿的只有 RenderObject
由于Widget是不可变的,所以我们不能让Widget直接去跟RenderObject联系来进行渲染工作,因为如果这样我们每次改变一个Widget下层的Widget都需要重新构建,这大大增加了底层渲染的成本。
但是Element是可变的,我们可以借助于Element来和RenderObject沟通,只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
在第一次创建 Widget的时候,会对应创建一个Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget进行比较,并且相应地更新 Element。重要的是,Element 被不会重建,只是更新而已。
An object in the render tree,RenderObject渲染对象,从名字我们就可以很简答的知道它是真正负责渲染的,负责最终的layout和paint操作。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
void markNeedsLayout() {
...
}
void markNeedsPaint() {
...
}
void layout(Constraints constraints, { bool parentUsesSize = false }) {
...
if (sizedByParent) {
performResize();
}
...
performLayout();
...
}
void performResize();
void performLayout();
void paint(PaintingContext context, Offset offset) { }
}
markNeedsLayout()
标记这个RenderObject
需要重新做布局,markNeedsPaint
标记这个RenderObject
需要重绘。
而渲染对象树在 Flutter 的展示过程分为四个阶段,即布局、绘制、合成和渲染。其中,布局和绘制在 RenderObject 中完成,Flutter 采用深度优先机制遍历渲染对象树,确定树中各个对象的位置和尺寸,并把它们绘制到不同的图层上。绘制完毕后,合成和渲染的工作则交给 Skia 搞定。
我们接下来通过StatelessElement来看下Element,在Widget中起了什么作用
首先我们看下基类Widget
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
///Inflates this configuration to a concrete instance.
@protected
Element createElement();
然后我们看下StatelessElement的源码
可以看到,StatelessWidget通过createElement方法生成了一个StatelessElement。(注意:这里是StatelessWidget作为参数传递了进去)
然后通过build方法创建Widget并且需要传入一个BuildContext
现在我们来看StatelessElement它做了什么。
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
我们接着来看这个widget.build(this)方法。咦?这里怎么把StatelessElement给传进去了?不是要的是BuildContext吗?难道StatelessElement就是BuildContext的子类?
继续看源码:
class StatelessElement extends ComponentElement {
/// 通过 StatelessWidget来创建StatelessElement
StatelessElement(StatelessWidget widget) : super(widget);
///通过父类获取Widget对象,StatelessWidget里createElement方法传入了StatelessWidget
@override
StatelessWidget get widget => super.widget as StatelessWidget;
///调用widget的build方法创建Widget,请注意这个传入初始化的值
@override
Widget build() => widget.build(this);
///更新Widget
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
好吧这就没错了,Element果然是BuildContext的子类那么这一切都说的通了。
接着往下面看,我们一直在调用build方法,我们在开发中也经常写包含Build的代码,到底怎么来绘制的
@override
Widget build(BuildContext context) {
return Container(
alignment:Alignment.center,
color: Colors.red,
child: Text("我是test1的内容区域"),
);
}
首先传入了一个Container,由于它是一个布局所以它并不直接参与绘制,它往往只参与布局工作,绘制工作往往由相关的子Widget或者相关属性Widget来进行绘制。
以上面的例子为例,我们来看Container的build方法
@override
Widget build(BuildContext context) {
Widget current = child;
if (alignment != null)
current = Align(alignment: alignment, child: current);
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color, child: current);
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
……
return current;
}
实际上Container会根据相关的属性拆封成对应的组件来构建,由源码来看,这个过程是依次执行绘制的。
依我们的例子为例,我们首先根据alignment进行位置构建,然后根据color进行构建,最后才返回这Wdiget。
可以看到,alignment和color对child分别执行了一直构建操作,最后交给了Text来进行处理。
说了这么多到底怎么来绘制啊,接着外下看
class Align extends SingleChildRenderObjectWidget {}
class Padding extends SingleChildRenderObjectWidget {}
class ColoredBox extends SingleChildRenderObjectWidget {}
class ColoredBox extends SingleChildRenderObjectWidget {}
Text的build方法中返回了一个RichText,所以Text本身也是RichText,这里不再贴这部分源码了。
class RichText extends MultiChildRenderObjectWidget {}
这两个类有一个共同的子类那就是RenderObjectWidget。
继续来看RenderObjectWidget方法
abstract class RenderObjectWidget extends Widget {
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
RenderObjectWidget是一个抽象类,MultiChildRenderObjectWidget和SingleChildRenderObjectWidget都是它的子类,这个类中同时拥有创建 Element、RenderObject,以及更新 RenderObject 的方法。
但实际上,RenderObjectWidget 本身并不负责这些对象的创建与更新。
对于 Element 的创建,Flutter 会在遍历 Widget 树,调用 createElement 去同步 Widget 自身配置,从而生成对应节点的 Element 对象。而对于 RenderObject 的创建与更新,其实是在 RenderObjectElement 类中完成的。
同样的来看RenderObjectElement
在 Element 创建完毕后,Flutter 会调用 Element 的 mount 方法。在这个方法里,会完成与之关联的 RenderObject 对象的创建,以及与渲染树的插入工作,插入到渲染树后的 Element 就可以显示到屏幕中了。
继续来看StatelessElement的update方法
abstract class RenderObjectElement extends Element {
RenderObjectElement(RenderObjectWidget widget)
@override
RenderObjectWidget get widget => super.widget as
@override
RenderObject get renderObject => _renderObject;
@override
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); attachRenderObject(newSlot);
_dirty = false;
}
@override void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false; }
}
判断新的widget是否与老的widget相同,如果不是同一个Widget就执行,遍历View树并移除子Widget,最后在原来的位置放上新的Widget。
对于StatelessElement它仅在第一个初始化和hot reload时会被调用,并且只会在应用active时有效。
到这里Flutter中Widget的创建基本流程就完成了,下篇我们来看下StatfulWidget的基本流程。