w4lle's Notes

人生如逆旅,我亦是行人。

w4lle's avatar w4lle

Flutter UI 渲染浅析(四)Build

系列文章的第四篇,本篇文章主要分析下 Element.rebuild() 过程。

源码基于 Flutter v1.20.4。

Flutter UI 渲染浅析(二)VSync 注册 这篇文章中提到,C++ Engine 接收到 VSync 信号后,需要做三件事情:

  • 执行 Dart Framework dart:ui 包下的 _beginFrame()
  • 执行 microtasks 任务
  • 执行 Dart Framework dart:ui 包下的 _drawFrame()

上一篇文章 Flutter UI 渲染浅析(三)Animation 原理 中分析了 _beginFrame() 的过程。

然后接着去处理在 Animate 过程中触发的 microtasks 任务,一般为 Ticker 或者 AnimationController 中 Future 的完成回调。

本篇文章分析下 _drawFrame() 的前半部分—— Element.rebuild() 的过程。

1、_handleDrawFrame()

同上篇文章的逻辑一样,调用到 Dart Framework 的SchedulerBinding._handleDrawFrame() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// lib/src/scheduler/binding.dart
void _handleDrawFrame() {
if (_ignoreNextEngineDrawFrame) {
_ignoreNextEngineDrawFrame = false;
return;
}
handleDrawFrame();
}
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
//结束Animate过程记录
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
//清除列表
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
//SchedulerPhase状态置为idle,等待下一次绘制触发
_schedulerPhase = SchedulerPhase.idle;
//结束Frame过程记录
Timeline.finishSync(); // end the Frame
_currentFrameTimeStamp = null;
}
}

主要做了两件事情:

  • 遍历 _persistentCallbacks,由 WidgetsBinding.addPersistentFrameCallback() 注册,从名字也可以看出,它是一个需要持久回调的列表,所以不可删除,每次绘制过程都会回调
  • 遍历_postFrameCallbacks,由 WidgetsBinding.addPostFrameCallback() 注册,只会回调一次,调用过后清除回调列表,一般用于监听绘制完成后处理一些任务

下面主要看下_persistentCallbacks 的执行过程。

1.1、RendererBinding.drawFrame()

Flutter UI 渲染浅析(二)VSync 注册 这篇文章中,我们简单分析了 7 个 Binding 类的作用及其初始化顺序。

RendererBinding 在初始化过程中,注册了_persistentCallbacks 回调,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
//初始化绘制管线
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
//注册window回调
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
//初始化RenderObject根节点RenderView
initRenderView();
_handleSemanticsEnabledChanged();
//这里添加_persistentCallbacks回调
addPersistentFrameCallback(_handlePersistentFrameCallback);
...
}
...
//_persistentCallbacks回调
void _handlePersistentFrameCallback(Duration timeStamp) {
//真正执行frame绘制
drawFrame();
_mouseTracker.schedulePostFrameCheck();
}

_handlePersistentFrameCallback(Duration timeStamp) 方法是 _persistentCallbacks 回调列表的一个子元素,其中去调用 drawFrame() 方法。

由于 WidgetsFlutterBinding 的混入顺序

1
2
3
4
5
6
7
8
// lib/src/widgets/binding.dart
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}

WidgetsBindingRendererBinding 之后,所以会先执行 WidgetsBinding.drawFrame() 方法。

1.2、WidgetsBinding.drawFrame()

WidgetsBinding.drawFrame() 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// lib/src/widgets/binding.dart WidgetsBinding
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
//构造BuildeOwner,主要负责Widget的build过程
_buildOwner = BuildOwner();
buildOwner.onBuildScheduled = _handleBuildScheduled;
//注册window相关回调
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
//导航channel
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
@override
void drawFrame() {
...
try {
//renderViewElement是根RenderObject RenderView对应的Element
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
//调用mixin的drawFrame方法,即RendererBinding.drawFrame()
super.drawFrame();
buildOwner.finalizeTree();
}
...
}
}
  • buildOwner.buildScope() 触发Widget Tree、Element Tree、RenderObject Tree三棵树的构建或刷新过程
  • super.drawFrame() 调用父类的 drawFrame() 方法,由于 WidgetsBinding 混入了 RendererBinding ,所以这里会去调用 RendererBinding.drawFrame() ,下篇文章会继续分析
  • buildOwner.finalizeTree() 卸载未激活状态的 Element 节点。未激活状态的节点在一个绘制帧周期内,是有可能被重新激活的,如果没有重新激活,那么就卸载掉

1.3、BuildOwner.buildScope()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// lib/src/widgets/framework.dart BuildOwner
void buildScope(Element context, [ VoidCallback callback ]) {
// 记录 Build 过程
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
_dirtyElementsNeedsResorting = false;
// 执行回调,在App启动构建三棵树时会用到
callback();
}
// 重排序,高度优先
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
// 触发 Elemeng.rebuild() 更新三棵树
_dirtyElements[index].rebuild();
}
index += 1;
// 在等待VSync 信号回调过程中,有可能又有新的标脏节点进来
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
// 重排序,高度优先
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
for (final Element element in _dirtyElements) {
// 清除 Element 脏标记
element._inDirtyList = false;
}
// 清空脏列表
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
// 结束 Build 记录过程
Timeline.finishSync();
}
}
  • 执行回调,在 App 启动时WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) 会用到,用于构建出三棵树,callback 为 element.mount(null, null);
  • _dirtyElements 脏列表重排序,在等待 VSync 信号回调过程中,有可能又有新的标脏节点进来
  • 脏列表中的节点,即调用了 State.setState() 的节点,触发 Element.rebuild() 更新三棵树,这里的重点也是Element.rebuild()。
  • 清除 Element 脏标记,清空脏列表

1.4、Element.rebuild()

1
2
3
4
5
6
7
8
9
10
// flutter/lib/src/widgets/framework.dart Elememt
void rebuild() {
...
if (!_active || !_dirty)
return;
performRebuild();
}
@protected
void performRebuild();

逻辑比较简单,调用 performRebuild(),它是一个空方法,实现在子类。

2、Widget、Element 与 RenderObject

在继续分析后续流程之前,先简单梳理下 Widget、Element 与 RenderObject 之间的关系,以及三棵树与 Layer Tree 之间的关系。

Flutter 开发者最熟悉的就是 Widget 了。

Widget 是面向开发者的接口,它是对UI的描述性表达,即是用于描述 Element 的配置的。

Widget 是声明式的 UI 结构,开发者通过组合 Widget 构建出想要的UI效果。

Widget 是不可变的(immutable),这就意味着每次刷新,都会重新构建出新的Widget对象,创建的开销很小,成本较低。

我们通常将 Widget 组合构建出的 UI 层级结构称为 Widget Tree,但相比 Element Tree,实际上并不存在 Widget Tree,由于 Widget 节点挂载在 Element 节点上,所以我们可以抽象为 Widget Tree。

Widget 提供 createElement()createRenderObject() (并不是所有)用于构建 Element 和 RenderObject。

Widget 主要有三种类型:

  • ProxyWidget 代理类,不直接参与构建UI,它们可以为其他 Widget 提供一些附加信息。例如 InheritedWidget 可以在其子树中传递附加信息;ParentDataWidget 用于提供其子树的布局信息
  • ComponentWidget 组合类,不直接参与绘制,它们用来组合包装用来构建复杂的UI布局。一般都是 StatefullWidget 或者 StatelessWidget 的子类,例如 RaisedButtonScaffoldTextGestureDetectorContainer
  • RenderObjectWidget 绘制类,可以构建出 RenderObject 用来布局和绘制

Element 是响应式编程的基础,频繁的创建 Element 会对性能有影响,所以只有在必要条件下才会创建一个新的 Element 对象,大部分情况下会进行复用,主要包含两个职责:

  • 持有 Widget 和 RenderObject 的引用,协调二者之间的数据绑定关系
  • 根据 Widget 的变化来创建或更新 Element Tree,包括挂载、更新、更改位置、卸载等

Element 和 Widget 是一一对应的关系,同样类型的 Widget 构建出同样类型的 Element。

RenderObject 用来布局和绘制,处理输入事件等。

Element 和 RenderObject 不是一一对应的,只有可以绘制的节点才有 RenderObject 对象。

Render Tree 用来布局和绘制 RenderObject 节点,最终生成 Layer Tree 提交给 C++ Engine。它的根节点是 RenderView。

他们三者之间的关系:

Element 持有 Widget 引用和 RenderObject 应用(可能没有),Widget 用来构建 RenderObject 对象。

对于 StatefullElement 来说,它还会持有 State 的引用。

这里需要注意,Element 的 child 是 Widget 中 build() 方法构建出来的 Widget 所对应的 Element,下面会用到。

Widget、Element、RenderObject 构建出三棵树 Widget Tree、Element Tree、Render Tree,它们共同组成了 Flutter 对于UI的组织描述。

前两棵树可以认为是面向开发者的,它们构成了声明式UI、响应式UI的基础,Render Tree 用来真正的布局和绘制,最后生成 Layer Tree,并保存在 Scene 对象中,提交给 C++ Engine 做光栅化合成。

那么,可不可以绕开 Widget、Element、RenderObject 来进行绘制,其实是可以的,它们只是用来组织描述绘制信息的,我们可以直接拿到 Canvas 进行绘制,只要最终可以生成Layer Tree 保存在 Scene 中就可以。

例如 这个例子 🌰

更进一步的,甚至可以绕过或者舍弃 Dart Framework,直接对接 C++ Engine,任何可以组织描述UI绘制结构的组织形式,理论上都可以桥接到 C++ Engine。

例如基于 W3C 标准的 CSS + JS/TS 组织的UI描述,通过绑定JS与C++ Engine,将绘制信息发送给 Engine,理论上也是可行的,如下图

3、Element.performRebuild()

继续上面分析到 Element.performRebuild()

分 Element 类型看下实现

ComponentElement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// lib/src/widgets/framework.dart ComponentElement
@override
void performRebuild() {
Widget built;
try {
...
built = build();
...
} catch (e, stack) {
_debugDoingBuild = false;
// 错误情况,这里是构建红屏的地方
built = ErrorWidget.builder();
} finally {
// 改变标志位
_dirty = false;
}
try {
...
_child = updateChild(_child, built, slot);
} catch (e, stack) {
// 错误情况,这里也是构建红屏的地方
built = ErrorWidget.builder();
_child = updateChild(null, built, slot);
}
}
// lib/src/widgets/framework.dart StatefullElement
@override
void performRebuild() {
if (_didChangeDependencies) {
// 依赖的祖先节点如果有变化,需要调用
_state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}

主要做了两件事情:

  • build() 构建子 Widget,注意这里是 子Widget
  • _updateChild() 创建或更新子Element,注意这里是 子Element

为什么强调子Widget和子Element,因为在这个Element对象中,它对应的 Widget 和 Element 就是Element自己合它持有的 Widget。这里很容易搞混。

RenderObjectElement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// lib/src/widgets/framework.dart RenderObjectElement
@override
void performRebuild() {
...
widget.updateRenderObject(this, renderObject);
}
// lib/src/widgets/basic Stack
class Stack extends MultiChildRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderStack renderObject) {
assert(_debugCheckHasDirectionality(context));
renderObject
..alignment = alignment
..textDirection = textDirection ?? Directionality.of(context)
..fit = fit
..clipBehavior = overflow == Overflow.visible ? Clip.none : clipBehavior;
}
}

widget.updateRenderObject() 的作用是把 Widget 中的属性值,绑定到 RenderObject 中,属性的类型一一对应。

3.1、Element.build()

build() 在各个类型的 Element 的实现:

1
2
3
4
5
6
7
8
9
10
11
// StatefullElement
@override
Widget build() => _state.build(this);
// StatelessElement
@override
Widget build() => widget.build(this);
// ProxyElement
@override
Widget build() => widget.child;

3.2、Element.updateChild()

这个方法是响应式UI的基础,也是 Dart Framework 的核心方法之一,看下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
// case 1
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
// case 2
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
// case 2.1
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
// case 2.2
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
// case 2.3
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// case 3
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}

首先要先明确,这个方法是用来更新子树的,第一个参数 child子Element,第二个参数newWidget子Widget

分几种情况:

  • case 1:newWidget 为空,也就是当前 Element 节点对应的 Widget build() 返回了空,那么标记child为非激活状态(当前帧绘制完成后会被卸载),然后返回空
  • case 2:如果child不为空,也就是之前构建过一次子Element
    • case 2.1:如果子Element对应的widget 即 child.widget 和新构建的 newWidget 相等,直接更新子widget,如果插槽不同,更新下插槽
    • case 2.2:如果 Widget.canUpdate(child.widget, newWidget) ,判断标准是 runtimeTypekey 都相等,那么调用 update() 更新 child
    • case 2.3:否则child不可复用,标记child为非激活状态(当前帧绘制完成后会被卸载),然后构建出一个新的 Element 节点,挂载到Element Tree上
  • case 3:否则 child 为空,不可复用,构建出一个新的 Element 节点,挂载到Element Tree上

下面看下几个关键的方法

3.3、Element.update()

Element 类实现:

1
2
3
4
5
// lib/src/widgets/framework.dart Element
@mustCallSuper // 子类复写该方法必须调用super
void update(covariant Widget newWidget) {
_widget = newWidget;
}

直接更新 Element 子节点的 _widget 引用。

子类复写该方法必须调用super。

看下子类的实现。

3.3.1、RenderObjectElement.update()

1
2
3
4
5
6
7
8
abstract class RenderObjectElement extends Element {
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
}

同上面一样,把 newWidget 中的属性值,绑定到 RenderObject 中。

SingleChildRenderObjectElementMultiChildRenderObjectElementRenderObjectElement 的子类,分别看下实现。

1
2
3
4
5
6
7
8
class SingleChildRenderObjectElement extends RenderObjectElement {
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
// 更新子树
_child = updateChild(_child, widget.child, null);
}
}
  • 调用super,复用 RenderObjectElement.update() 逻辑
  • 更新子树
1
2
3
4
5
6
7
8
class MultiChildRenderObjectElement extends RenderObjectElement {
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
}
  • 调用super,复用 RenderObjectElement.update() 逻辑
  • 通过差分算法将新构造的 widget.children 绑定到已有的 _children 上来更新子树,updateChildren() 逻辑虽然看起来很多,但是还比较好理解,这里就不放源码了,说下逻辑
    • 首先从 topIndex 到 bottomIndex 遍历 oldChildElement 和 newChildWidget,如果Widget.canUpdate(oldChild.widget, newWidget),那么updateChild()更新子树 updateChild(),直到匹配失败,记录 topIndex 累加值
    • 从 bottomIndex 到 topIndex 遍历oldChildElement 和 newChildWidget,直到匹配失败,记录 bottomIndex 累减值,这里不更新子树
    • 遍历缩小了的 oldChildElement 列表,记录 oldChild.widget.key 和 oldChild 到 map,key为空的反激活
    • 遍历缩小了的 newChildWidget 列表,匹配 map 中的key类型,updateChild() 更新子树,未匹配到的反激活
    • 最后更新第二步得到剩余的部分

3.3.2、StatefullElement.update()

1
2
3
4
5
6
7
8
9
10
11
12
13
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
// Notice that we mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
_state._widget = widget as StatefulWidget;
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
// 更新子树
rebuild();
}
  • 调用 super
  • 更新 state 中 widget 的引用
  • 调用 _state.didUpdateWidget(oldWidget)
  • 调用 rebuild() 更新子树,由于 rebuild() 一定会触发 build() 方法调用,所以这里进行标脏

StatelessElement.update() 类似,不写出来了。

3.4、Element.inflateWidget()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
// 如果key类型是 GlobalKey,从非激活状态的列表中尝试匹配类型相同的节点,抢救复用一下
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
// 通过 Widget,创建对应的 Element
final Element newChild = newWidget.createElement();
// 挂载到 Element Tree 上
newChild.mount(this, newSlot);
return newChild;
}
  • 如果key类型是 GlobalKey,从非激活状态的列表中尝试匹配类型相同的节点,抢救性复用一下
  • 通过 Widget,创建对应的 Element
  • 挂载到 Element Tree 上

3.5、Element.mount()

挂载到Element Tree上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
// 更新父节点信息
_parent = parent;
// 更新插槽信息
_slot = newSlot;
// 更新深度
_depth = _parent != null ? _parent.depth + 1 : 1;
// 从初始化状态更改为激活状态
_active = true;
if (parent != null)
// 绑定 BuildOwner 对象
_owner = parent.owner;
final Key key = widget.key;
if (key is GlobalKey) {
// 如果是 GlobalKey,注册到公共map,全局复用
key._register(this);
}
// 从 parent 更新 _inheritedWidgets,用于传递附加信息
_updateInheritance();
}
  • 更新父节点信息
  • 更新插槽信息
  • 更新深度
  • 从初始化状态更改为激活状态
  • 绑定 BuildOwner 对象
  • 如果是 GlobalKey,注册到公共map,全局复用
  • 从 parent 更新 _inheritedWidgets,用于传递附加信息

如果子类复写该方法,那么必须要调用 super。

3.5.1、ComponentElement.mount()

1
2
3
4
5
6
7
8
9
10
11
abstract class ComponentElement extends Element {
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();
}
}
  • 调用 super() 调用 Element.mount()
  • 调用 _firstBuild() -> rebuild() -> performRebuild() 构建 Element 子树

下面还有 SingleChildRenderObjectElement 、MultiChildRenderObjectElement 等子类,逻辑跟update()差不多,就不列出来了。

3.5.2、RenderObjectElement.mount()

1
2
3
4
5
6
7
8
9
10
11
abstract class RenderObjectElement extends Element {
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
// 构建 RenderObject
_renderObject = widget.createRenderObject(this);
// 将 RenderObject 挂载到 Render Tree 上
attachRenderObject(newSlot);
_dirty = false;
}
}
  • 调用 super() 调用 Element.mount()
  • 构建 RenderObject
  • 将 RenderObject 挂载到 Render Tree 上

3.6、RenderObjectElement.attachRenderObject()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class RenderObjectElement extends Element {
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
// Element Tree 向上遍历祖先节点,找到第一个 RenderObject 节点
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
// 根据规则插入到 Render Tree 中,需要子类实现
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
// Element Tree 向上遍历祖先节点,找到第一个 ParentDataElement 节点
final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {
...
parentDataWidget.applyParentData(renderObject);
}
}
  • Element Tree 向上遍历祖先节点,找到第一个 RenderObject 节点
  • 根据规则插入到 Render Tree 中,需要子类实现
  • Element Tree 向上遍历祖先节点,找到第一个 ParentDataElement 节点,ParentDataElement 节点中记录着布局位置信息,如果没有找到返回空
  • 根据 ParentDataElement 找到对应的 ParentDataWidget,调用 applyParentData()

其中,ParentDataElement 根据参数绑定了 ParentDataWidget 类型,并通过泛型绑定了 ParentData 类型。

3.6.1、ParentDataWidget.applyParentData()

ParentDataWidget 是 ProxyWidget 的子类,它的子类包括 Flexible、LayoutId、Positioned、KeepAlive 等Widget。

ParentDataWidget 使用泛型绑定了 ParentData 类型。

以 Flexible 为例看下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Flexible extends ParentDataWidget<FlexParentData> {
@override
void applyParentData(RenderObject renderObject) {
final FlexParentData parentData = renderObject.parentData as FlexParentData;
bool needsLayout = false;
if (parentData.flex != flex) {
parentData.flex = flex;
needsLayout = true;
}
if (parentData.fit != fit) {
parentData.fit = fit;
needsLayout = true;
}
if (needsLayout) {
final AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
}
  • 更新布局属性信息
  • RenderObject 节点 Layout 标脏,记录在 BuildOwner._nodesNeedingLayout 列表中,等待下一步 Layout 处理

4、总结

本篇文章介绍了 WidgetsBinding.drawFrame() 的过程,以及 Widget、Element、RenderObject 及三棵树的关系,梳理了build() 过程在三棵树之间的流转关系,通过 Element Tree 和 Widget Tree 构建了 Render Tree,最终触发 RenderObject.markNeedsLayout() Layout 标脏操作,记录在 BuildOwner._nodesNeedingLayout 列表中,等待下一步 Layout 处理。

下一篇文章将继续分析 RendererBinding.drawFrame() 中 Layout 过程。

参考

本文链接: http://w4lle.com/2020/11/16/flutter-ui-build/

版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2020/11/16/flutter-ui-build/