2、Widget、Element、RenderObjectapp
7、Paint 绘制(2)post
9、Flutter 小实践this
在监听 vsync 信号回调时会调用 drawFrame 函数spa
(1) drawFramedebug
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
superd.drawFrame();
}
复制代码
在 drawFrame 方法中,则调用了 buildScope 则是 Build 流程
(2) buildScope
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
_dirtyElements[index].rebuild();
index += 1;
}
}
复制代码
遍历 _dirtyElements, 从新 rebuild
(3)Element -> rebuild
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
复制代码
rebuild 方法中主要是调用了 performRebuild
(4) Element -> performRebuild
这个类是由具体的类继承的,如以 Text widget 为例, 其 对应的 element 是 StatelessElement, 而 StatelessElement 继承 ComponentElement
(5)ComponentElement --> performRebuild
@override
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
复制代码
(5)build
StatelessElement 重写 了 componentElement 的build 的方法
@override
Widget build() => widget.build(this);
复制代码
因而可知,build 方法实际调用的就是咱们 平时写组件时的 build,这个方法会返回新的 widget 树,新的widget 树有什么用呢?且看接下来调用的 updateChild
(6)updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null) {
deactivateChild(child); // 第一种状况
}
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第二种状况
}
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot); // 第三种状况
}
child.update(newWidget);
return child;
}
deactivateChild(child);
}
return inflateWidget(newWidget, newSlot); // 第四种状况
}
复制代码
这个方法主要是涉及 Element 的更新逻辑,更新规则以下
第一种状况: build出来的widget等于null,也就是newWidget 是null, 说明这个控件被删除了,child Element能够被删除了
第二种状况:child的 widget 和新 build 出来的同样,则判断 slot(父级设置的信息,以定义此子级在其父级的子级列表中的位置) 是否一致,不一致则更新,Element仍是旧的Element
第三种状况:canUpdate(判断key值和 runtimeType 是否一致) 为 true 时, 更新 Element 便可
第四种状况:以上三种状况都不知足时,建立新的Element
(7) inflateWidget
这个方法主要是用于建立新的 Element
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
// 寻找有没有可用的Element
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
// 建立 Element
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
复制代码
建立了新的 Element 以后,则会调用 elment 的 mount 方法,完成 element 树的挂载,其中,mount 方法是由具体的子类实现的,ComponentElement 类的 mount 方法会递归遍历子节点,调用子节点的 rebuild方法: _firstBuild -> rebuild -> performRebuild ,而 RenderElement 类的 mount 方法 则会调用 widget 的 createRenderObject 建立对应的 renderObject, 并renderObject 插到对应的 render树上。
(8) ComponentElement --> mount
循环遍历子节点
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
复制代码
(9) RenderElement --> mount
建立 renderObject 而且 将 renderObject 插入到 render 树上
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); // 建立renderObject
attachRenderObject(newSlot); // 将renderObject 插入到render树上
_dirty = false;
}
复制代码
在Vsync 信号回调时会触发 build 流程,可是首帧渲染并无等待 Vsync 信号的回调,即在 app 启动初始化时,element 树又是怎样构建的呢
(1) runApp
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
复制代码
在 runApp 方法中,完成了 Binding 的初始化后,调用了 attachRootWidget 方法
(2)attachRootWidget
在这个方法中,经过 RenderObjectToWidgetAdapter 建立根elememnt _renderViewElement, 其中 renderView 是根 renderObject
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}
复制代码
(3)attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
owner.buildScope(element, () {
element.mount(null, null);
});
return element;
}
复制代码
其实这里也是将 根 element 挂载同时遍历建立子节点,这个流程跟上面分析的build流程是相似的