前言
写Flutter已有好几个月的时间了,最开始总会有一点点不适应,但是写一段时间后还是觉得蛮顺手的,而且支持热更,不需要等整个项目编译,提升了不少效率。但是在开发过程中,总是会遇到因为Widget嵌套得不好而出现一些错误,于是一直想学习一下它的相关原理看看是为什么。所以通过学习和参考网上大神分享的的一些资料,整理出了今天要分享的文章。
正文
Flutter有三棵重要的树,分别是Widget树、Element树、RenderObject树,它们各司其职,分成了几个相关联但清晰的结构。Widget树与我们日常开发接触最多,其它的两棵树比较少接触到。
这三棵树的关联的大致流程:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,再完成布局排列和绘制。最后合并层级,通过Skia引擎渲染为GPU数据,然后GPU接着将数据交给显示器显示。
它们三者的联系如下图:
如果最开始有人告诉你写Flutter就像在写配置,你会不会觉得不太可能。那么一起通过下面的内容看看这句话到底是不是真的。
Widget
Widget只是UI元素中的配置数据,并且一个Widget可以对应多个Element。真正的UI渲染树是由Element构成的。
日常开发中,常见结构如下图:
重要成员:
- Key:跟Widget的runtimeType一起决定此Widget是否复用
- createElement方法:创建对应的Element
- canUpdate方法:对比runtimeType和Key,相等的话表示就会用新的Widget去更新Element,否则的话会重新创建Element对象。
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
StatelessWidget:
- 用于不需要维护状态的Widget
- createElement方法返回的是StatelessElement对象
StatefulWdiget
- createElement方法返回的是StatefulElement对象
- createState方法返回一个State对象,维护状态信息
State:
- 在第一次插入到树中被创建,它的widget成员可能会被更新,在mount的时候,会调用firstBuild方法,firstBuild方法会调用rebuild方法
- 调用setState方法后,会调用对应Element的markNeedsBuild:被标记为dirty,并且会调用owner.scheduleBuildFor(this),然后会触发rebuild。
Element
mount方法中调用Widget去创建一个RenderObject,创建的RenderObject会被Element持有;然后插入到渲染树中。(RenderObjectElement等等),代码如下:
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
//省略
attachRenderObject(newSlot);
_dirty = false;
}
- 创建一个RenderObject
- 加入到渲染树
当配置改变,Widget会根据runtimeType和Key去比较,判断是否可复用Elemen,可复用的话,则更新Element的配置,否则新建一个(Widget中的canUpdate方法决定)
rebuild方法会调用performRebuild方法
void performRebuild() {
Widget built;
try {
built = build();
} catch (e, stack) {
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
}
try {
_child = updateChild(_child