级别: ★☆☆☆☆
标签:「Flutter」「Element 」「重要方法与调用时机 」
作者: 沐灵洛
审校: QiShare团队
上一篇我们简单介绍了Flutter中Element
是什么以及它的生命周期。
本篇我们将与大家一起探讨Element
的一些重要的方法的作用以及调用时机。
Element的重要方法
void mount(Element parent, dynamic newSlot)
描述:在parent element
中给定的插槽上,加入当前的 element
。
调用时机:新创建的element
首次加入树中时,
Element updateChild(Element child, Widget newWidget, dynamic newSlot)
描述:使用新的配置widget
,更新树中element
。这个方法是widges
系统的核心方法。
调用时机:每次基于更新后的widget
,添加、更新或移除树中的child element
时。
方法原理:
newWidget == null | newWidget != null | |
---|---|---|
child == null | Returns null. | Returns new [Element]. |
child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
///newWidget== null 弃用element
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
///标志位:解决hot reload前后Widget的类型不一致导致的类型错误 。
///比如:之前是`StatefulWidget`(StatefulElement) ,现在是`StatelessWidget`(StatelessElement) 。
bool hasSameSuperclass = true;
assert(() {
///element is StatefulElement ? 1 : element is StatelessElement ? 2 : 0;
final int oldElementClass = Element._debugConcreteSubtype(child);
/// widget is StatefulWidget ? 1 : widget is StatelessWidget ? 2 : 0;
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
///确保类型一致
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
///widget相等
if (hasSameSuperclass && child.widget == newWidget) {
///只有一个child的`element`,它的`slot`为null
if (child.slot != newSlot)
///作用:修改给定的`child`在其`parent`中占据的slot。
///调用时机:`MultiChildRenderObjectElement`
///和其他拥有多个渲染对象的`RenderObjectElement`的子类。
///当它们的`child`从`element`的`child`(renderObject)数组
///的一个位置移动到其他位置时。
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
///询问Widget.canUpdate
if (child.slot != newSlot)
///修改给定的`child`在其`parent`中占据的slot。
updateSlotForChild(child, newSlot);
///使用新的配置,更新当前child element
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
/// 弃用当前的child element
deactivateChild(child);
assert(child._parent == null);
///注入新的widget
newChild = inflateWidget(newWidget, newSlot);
}
} else {
///child == null 创建新的
newChild = inflateWidget(newWidget, newSlot);
}
assert(() {
if (child != null)
_debugRemoveGlobalKeyReservation(child);
final Key key = newWidget?.key;
if (key is GlobalKey) {
key._debugReserveFor(this, newChild);
}
return true;
}());
return newChild;
}
void update(covariant Widget newWidget)
描述:使用新widget
修改用于配置此element
的widget
。
调用时机:当parent element
希望使用不同的widget
更新此element
时,并且新的widget
必须保证和旧的widget
拥有相同的runtimeType
。
Element inflateWidget(Widget newWidget, dynamic newSlot)
描述:为给定的widget
创建一个element
,并且将其作为child
添加到当前element
的给定slot
中。
调用时机:此方法通常由updateChild
调用,但也可以由需要对创建的element
进行更细粒度控制的子类直接调用。
注意: 如果给定的widget
有global key
,并且其对应的element
已经存在,这个函数将复用此element
(可能从树中的另一个位置移植它,或从inactive
元素列表中重新激活它)而不是创建一个新的元素。此方法返回的element
已经被mounted
并且处于active
状态。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
assert(newWidget != null);
final Key key = newWidget.key;
///判断是否是`GlobalKey`
if (key is GlobalKey) {
///从处于`inactive`的列表中,重新获取。
///1.final Element element = key._currentElement;
///2.element == null或!Widget.canUpdate(element.widget, newWidget)
///reture null
///3. final Element parent = element._parent;
///4. parent.forgetChild(element);
///5. parent.deactivateChild(element);
///6.owner._inactiveElements.remove(element);
///7.return element
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
///重新激活获得的`element`。
///1.修改`parent`
///2._updateDepth(int parentDepth)
///3.递归遍历修改`element`中所有子元素的状态未`active`.
///针对拥有`children`的`element`。
///4.调用attachRenderObject(dynamic newSlot)
///添加渲染对象到树中。
newChild._activateWithParent(this, newSlot);
///使用新的`widget`,更新此`element`,在新的`slot`上
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
///返回updatedChild
return updatedChild;
}
}
///如果不含globalKey
///创建新的元素
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
///挂载:加入树中。
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}
void deactivateChild(Element child)
描述:从inactive elements
列表中移除给定的element
并且 从渲染树中分离此element
所对应的render object
。
调用时机:一般在元素更新(删除)其子元素时调用,但是在global key
更换父节点时,新父会主动调用旧父的deactivateChild
,并且在此之前会先调用旧父的forgetChild
,对旧父进行更新。
void deactivateChild(Element child) {
assert(child != null);
assert(child._parent == this);
///去父
child._parent = null;
///分离其渲染对象
child.detachRenderObject();
///加入不活跃的数组中
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle) {
if (child.widget.key is GlobalKey)
debugPrint('Deactivated $child (keyed child of $this)');
}
return true;
}());
}
void detachRenderObject()
描述:从渲染树中移除渲染对象。
调用时机:一般在deactivateChild
方法中被调用
注意:该函数的默认实现只是对其child
递归调用detachRenderObject
,但是RenderObjectElement.detachRenderObject
重写了此方法后,对其进行了覆盖(未调用super
)。
void detachRenderObject() {
visitChildren((Element child) {
child.detachRenderObject();
});
_slot = null;
}
void forgetChild(Element child)
描述:从元素的child
列表中删除给定的child
,以准备在元素树中其他位置重用该child
。
调用时机:一般在deactivateChild
方法调用前。update
会负责创建或者更新用于替换此child
的新的child
。
关于对此方法的详细用法可以查看MultiChildRenderObjectElement
类,此类中forgetChild
会影响update
时对于replaceWithNullIfForgotten
方法的调用,也会影响visitChildren
时对于子元素的遍历。
void updateSlotForChild(Element child, dynamic newSlot)
描述:修改给定的child
在父元素中占据的插槽 。
调用时机:MultiChildRenderObjectElement
和其他拥有多个渲染对象的RenderObjectElement
的子类。当它们的child
从element
的child
(renderObject)数组的一个位置移动到其他位置时。
void updateSlotForChild(Element child, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(child != null);
assert(child._parent == this);
void visit(Element element) {
element._updateSlot(newSlot);
if (element is! RenderObjectElement)
element.visitChildren(visit);
}
visit(child);
}
void activate()
描述:将元素的生命周期由inactive
变为active
。
调用时机:当之前停用的元素重新合并到树中时,框架将调用此方法。元素第一次激活时(即,状态从initial
开始时),framework
不会调用此方法,而是通过调用mount
。
void activate() {
....
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true;
// We unregistered our dependencies in deactivate, but never cleared the list.
// Since we're going to be reused, let's clear our list now.
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
assert(() {
_debugLifecycleState = _ElementLifecycle.active;
return true;
}());
if (_dirty)
///将元素添加到`dirty element`列表中,
///以便在`WidgetsBinding.drawFrame`调用`buildScope`
///时将对其进行重建。
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
void attachRenderObject(dynamic newSlot)
描述:将renderObject
添加到渲染树中slot
指定的位置。
调用时机:该函数的默认实现只是对其child递归调用attachRenderObject,但是RenderObjectElement.attachRenderObject重写了此方法后,对其进行了覆盖(未调用super)。
void attachRenderObject(dynamic newSlot) {
assert(_slot == null);
visitChildren((Element child) {
child.attachRenderObject(newSlot);
});
_slot = newSlot;
}
void unmount()
描述: 生命周期由inactive
变为 defunct
。
调用时机:当framework
确定处于inactive
状态的元素永远不会被激活时调用。在每个动画结束时,framework
会对任何保持inactive
状态的元素调unmount
,以防止inactive
元素保持inactive
的时间超过单个动画帧。调用此函数后,该元素将不再合并到树中。
void reassemble()
描述: 用于快速开发的一种debugging
策略。
调用时机: 在调试期间重新组装(reassemble
)应用程序时调用,例如:hot reload
期间。此函数仅在开发期间被调用,并且调用了reassemble
,build
将至少被调用一次。
void didChangeDependencies()
调用时机: 当此元素的依赖项更改时调用。比如 此元素依赖的InheritedElement
更新了新的InheritedWidget
并且InheritedWidget.updateShouldNotify
返回true
的时候,framework
会调用此方法,通知此元素做出相应的改变。
void markNeedsBuild()
描述: 将元素标记为dirty
,并将其添加到全局的widget
列表中,以在下一帧中进行重建。
调用时机: setState
、reassemble
、didChangeDependencies
时。
void rebuild()
调用时机:
- 当
BuildOwner.scheduleBuildFor
已经标记此元素为dirty
时,由BuildOwner
调用; - 当元素通过
mount
首次被构建时会调用; - 当元素的
widget
通过update
已经被改变时,会调用此方法。
Flutter中的Element(上篇)
iOS 解决 [NSURL fileURLWithPath:] 对 # 的编码问题
Xcode 调整导航目录字体大小b
Swift 5.1 (21) - 泛型
Swift 5.1 (20) - 协议
Swift 5.1 (19) - 扩展
Swift 5.1 (18) - 嵌套类型
Swift 5.1 (17) - 类型转换与模式匹配
浅谈编译过程
深入理解HTTPS