序言
使用flutter
进行开发也有一段时间了,今天小轰来聊聊widget
、element
、renderObject
三者间的结构关系。
三棵树
- Widget 树负责配置信息,我们平时写代码写的就是这棵树。
- RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的。
-
Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。
从上图可以看出,widget 树和 Element 树节点是一一对应关系,每一个 Widget 都会有其对应的 Element。但是 RenderObject 树则不然,只有需要渲染的 Widget 才会有对应的节点。
Widget
我们平时用 Widget 使用声明式的形式写出来的界面,可以理解为 Widget 树,这是要介绍的第一棵树。下面,我们来看看 Widget 的结构设计:
-
widget 分为两种类型
widget 从渲染的角度进行分类,分为 可渲染Widget
与 不可渲染Widget
。像我们常用的 statelessWidget 与 statefulWidget 就属于不可渲染的Widget。
-
widget 内部结构
如上图所示:
- 每个widget都提供
createElement
方法,每个widget最终都会转化成Element
; - widget被触发
build
方法的时机特别频繁,canUpdate
方法维护Element复用机制
。当返回true时,复用旧的Element; - 只有可渲染的widget(子类
RenderObjectWidget
)提供生成RenderObject
的方法
Element
与widget
的分类相对应,element
也区分是否可渲染,继承关系如下:
从上图中我们得知,StatefulElement 在其构造方法中调用了
widget.createState
方法,并赋值 _state 其 widget 对象。
这就是 statefuleWidget createState 方法被调用的时机
。
重点:Element 内部结构分析
整理总结
- Element 持有外部 Widget 对象。
- Element 提供获取 RenderObject 的方法
(get renderObject)
。[从自己开始往子节点遍历,直到找出RenderObjectElement
,RenderObjectElement 提供生成RenderObject 的能力] - RenderObjectElement 通过调用
widget.createRenderObject(this)
生成RenderObject
- 核心方法 mount()`
componentElement
的mount方法主要作用是执行build(根据类型区分widget.build
,state.build
)renderObjectElement
的mount方法主要作用是生成RenderObject- Element创建完成时就会调用
mount
, 调用顺序为 mount -> _firstBuild -> reBuild -> performRebuild -> build- Element.markNeedsRebuild 会重新走 reBuild
RenderObject
成员方法介绍:
parentData
: 由父节点赋值,父RenderObj会将子RenderObj的相关数据存储在子元素的parentData中。如在 Stack 布局中,RenderStack就会将子元素的偏移数据存储在子元素的parentData中(具体可以查看Positioned
实现)。layout()方法
: 接收两个参数,constrains
为父节点对子节点的大小限制;parentUsesSize
标识本节点布局发生变化时父节点是否同步发生重布局操作。_relayoutBoundry
: 在layout()方法中进行赋值,当parentUsesSize等于false时,_relayoutBoundry = this(当前RenderObject对象),表示它的大小变化不会影响到parent的大小。否则,_relayoutBoundry = = (parent! as RenderObject)._relayoutBoundary;markNeedsLayout()
: 当一个Element标记为 dirty 时便会重新 build,这时RenderObject便会重新布局,我们是通过调用 markNeedsBuild() 来标记Element为 dirty 的。
从自身开始向parent遍历,直到找到是 relayoutBoundry 的 RenderObject 为止。然后将其标记为 dirty,重新build。
void markNeedsLayout() { ...省略 if (_relayoutBoundary != this) { markParentNeedsLayout(); } else { ...省略 } }
-
performResize()
: 在layout方法中,只在sizedByParent
为 true 时,才会被调用。 -
performLayout()
: 在layout方法中被调用,每次layout都会触发
题外话,State中setState做了什么?
State.setState() -> _element.markNeedBuild() -> dirty=true -> readerObject.markNeedLayout()