Flutter 中有三棵树:
Widget 树,Element 树和 RenderObject 树。
1、Widget: 这是一个配置树,只用于存放渲染内容、视图布局信息, widget的属性最好都是immutable
2、Element: 存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject
3、RenderObject:根据Widget的布局属性进行layout,paint Widget传人的内容
当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。
最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。
Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如MultiChildRenderObjectElement。最终所有Element的RenderObject构成一棵树,我们称之为”Render Tree“即”渲染树“。
总结一下,我们可以认为Flutter的UI系统包含三棵树:Widget树、Element树、渲染树。他们的依赖关系是:根据Widget树生成Element树,再依赖于Element树生成RenderObject 树,
组件的更新
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
我们可以看到Widget.canUpdate的定义,通过runtimeType和key比较来判断;
如果可以更新,更新Element子节点;
否则deactivate子节点的Element,根据newWidget创建新的Element。
另外canUpdate判断是有层级的,如果当前Widget返回false,子组件将不再进行判断,会随着父节点一起重建。所以单纯设置key无法保证节点不会重建
看一下setState 的机制
1、StatefulWidget便提供了这样的机制,通过调用setState((){})标记自身为dirty状态,以等待下一次系统的重绘检查。
2、在State类中的调用setState((){})更新视图,将会触发State.build! 也将间接的触发其每个子Widget的构造方法以及build方法。
3、Flutter并没有实现数据双向绑定,你在State.setState((){})中写什么代码都不重要,它仅用来标记这个State对象需要重新Build,重新build后根据已变更的数据来创建新的 Widget。
开发中如何选择StatefulWidget和StatelessWidget?
-
优先使用 StatelessWidget
-
含有大量子 Widget(如根布局、次根布局)慎用 StatefulWidget
-
尽量在叶子节点使用 StatefulWidget
-
将会调用到setState((){}) 的代码尽可能的和要更新的视图封装在一个尽可能小的模块里。
-
如果一个Widget需要reBuild,那么它的子节点、兄弟节点、兄弟节点的子节点应该尽可能少
-
RepaintBoundary 它为经常发生显示变化的内容提供一个新的隔离layer,新的layer paint不会影响到其他layer
4.3 绘制过程
RenderObject可以通过 paint() 方法来完成具体绘制逻辑,流程和布局流程相似,子类可以实现
paint() 方法来完成自身的绘制逻辑,
签名如下:
void paint(PaintingContext context, Offset offset) { }
复制代码
通过 context.canvas 可以取到 Canvas 对象,接下来就可以调用Canvas API来实现具体的绘制逻辑。
如果节点有子节点,它除了完成自身绘制逻辑之外,还要通过paintChild() 方法来调用子节点的绘制方法。
如此递归完成整个节点树的绘制,最终调用栈为:
paint() > paintChild() > paint() ...。
五、为什么需要三棵树?
先说答案:使用三棵树的目的是尽可能复用 Element。
复用 Element 对性能非常重要,因为 Element 拥有两份关键数据:Stateful widget 的状态对象及底层的 RenderObject。当应用的结构很简单时,或许体现不出这种优势,一旦应用复杂起来,构成页面的元素越来越多,重新创建 3 棵树的代价是很高的,所以需要最小化更新操作。当 Flutter 能够复用 Element 时,用户界面的逻辑状态信息是不变的,并且可以重用之前计算的布局信息,避免遍历整棵树。