Widget、Element、RenderObject之间的关系
Widget
Widget是对UI的一种描述,可以理解为UI的配置项,定义了UI的外观和行为。Widget的种类有很多,一般是按照组合类、渲染类等区分,但是在实际的使用中按照功能和使用场景区分,可能理解上会更方便。
1.内容展示
不管是什么平台,平时用的最多的无非是文本、图片、按钮这些。
因此我们需要熟练掌握的也就是Text、Text.rich、Image、Button、TextField等。
在使用TextField的时候,要注意安卓手机上焦点的问题(尤其页面上有多个TextField);还有就是其父组件是Column时,键盘的出现可能导致约束出问题;
2.元素排列
用于构建UI的基本布局结构,控制组件的排列方式,类似CSS。
这类Widget组件有Row、Column、Flex、Stack、Center、Padding、Container、Wrap等
3.交互类
在构建一个功能时,不光只有展示,还有一些交互,比如滚动、点击、拖拽、输入等。
这类Widget组件有ListView、GridView、GestureDetector、InkWell、SingleChildScrollView、Draggable等。
4.系统类
对于App开发来说,我们经常面对的就是一个个页面,而页面是必不可少的有导航栏、路由等;
用到的基本就是Scaffold、AppBar,这类也都会根据公司的整体设计风格封装成业务组件。
5.约束类
约束类的组件主要对其子组件做一个尺寸上约束,这类组件有LayoutBuilder、LimitedBox、SizedBox、ConstrainedBox等,这几个组件在工作中使用较为频繁。
对于初学者来说,掌握上面这些Widget,已经可以实现95%的业务需求,一些特殊的交互和场景我们在遇见的时候再去查找学习就好了。在平时的开发中,还有系统提供的功能性组件,可以简化我们的工作,比如FutureBuilder、Opacity、Visibility、ValueListenableBuilder等。
但是有一种场景,就是需要我们提前知道文本占用的尺寸,然后再去排列子组件,这种情况处理起来就比较麻烦一些。我的方案是,给显示的Widget添加GlobalKey,利用帧调度的帧后回调去做一次布局计算,然后通过GlobalKey获取尺寸,再重新渲染一次,当然这需要一些变量去控制状态。
RenderObject
RenderObject是Flutter渲染系统的核心抽象类,不可被实例化,所有显示在屏幕上的UI都是RenderObject的子类,负责实现布局(layout)、绘制(paint)、命中测试(hit testing)和语义化(semantics)等底层渲染逻辑。它是渲染树(render tree)的基本节点,所有可见 Widget 最终都会通过RenderObjectWidget创建对应的RenderObject来完成实际渲染工作。
在实际开发的时候并不需要关心RenderObject子类的具体实现,这里做个TODO作者本人目前还没详细学习RenderObject。
Element
正确的创建顺序应该是Widget->Element->RenderObject,作者是故意调整了一下顺序,一是因为RenderObject的详细内容还没有学习到,二是Element作为Widget与RenderObject的桥梁需要着重学习下(主要也刚好看到这里)。
Element是抽象类,实现了BuildContext中定义的方法。同时BuildContext也是一个抽象类,表示了Widget的上下文信息,由Element具体实现,在开发中我们碰到的context,实际就是Element的具体实现。
BuildContext
BuildContext中定义了字段的get方法、获取祖先Widget和State的方法、跨Widget获取共享数据的方法。
context的生命周期与 Widget 一致:当 Widget 被从树中移除时,其对应的context会失效,此时使用该context可能导致崩溃(如异步操作中误用已销毁的context)。- 避免在
initState中直接使用context:initState执行时,Widget 还未完全插入到树中,此时调用findAncestor...等方法可能返回null。如需使用,可通过WidgetsBinding.instance.addPostFrameCallback延迟到第一帧构建完成后获取。 context是 “位置标识” :不同 Widget 的context不同,即使是同一类型的 Widget,位于树中不同位置时,context也不同(对应不同的Element)。
Element的实现
Element的生命周期- 将
Widget作为配置数据,通过调用Widget.createElement方法创建element; - 调用
[mount]方法将新创建的 element 添加到树中(父节点给定的插槽);[mount]方法负责膨胀 widget,并在必要的时候调用[attachRenderObject]将关联的 render objects 关联到 渲染树上,此时element的状态是 “active”的,并且可能已经显示到屏幕上; - 某些情况父级可能会变更子
Element的Widget,例如,因为父级使用新状态重建,此时框架将使用新 Widget 调用[update]方法 - 某些情况父节点可能决定从树中移除 element,父节点通过调用自己的[deactivateChild]方法实现。此父节点将从 render tree 中移除 element's 的 render object,并将 element 添加到
[owner]的inactive列表,框架将会触发 element 调用[deactivate]; - 此时,该 element 将处于“
inactive” 状态,并且不会出现在屏幕上。直到当前帧动画结束前,element 一直保持 “inactive” 状态,在帧动画结束时,处于 "inactive"状态的 element 将会被卸载; - 如果 element 被重新合并到树中(比如一或多个父节点通过 global key 复用了此 element),element 将会从
[owner]的inactive列表中移除,并调用[activate]方法,并将 element 的 render object 添加到 render tree 中(此时,element 在此成为 “active" 状态,并可能出现在屏幕上)。如果 element 在当前帧动画结束时,没有重新加到树中,element 将会调用[unmount]。此时,element 的状态为 ”defunct“,并且不会在被加到树中。
- 将
核心字段
/// 通过给定的 Widget 作为配置项,创建 Element 对象
/// 通常通过覆盖[Widget.createElement]调用
Element(Widget widget): _widget = widget;
/// 父 Element,类型为`Element?`,用于维护 Element 树的层级关系。
Element? _parent;
/// Element 的配置信息
Widget? _widget;
@override Widget get widget => _widget!;
/// 是否完成挂载到Widget Tree中
bool get mounted => _widget != null;
/// 负责调度 Element 的重建、清理等全局操作;
BuildOwner? _owner;
@override BuildOwner? get owner => _owner;
/// `depth`:当前 Element 在树中的深度(整数),用于确定渲染顺序(深度小的先渲染)。
late int _depth;
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
/// 如果 Element 被标记需要 rebuilding,则该值为 true
bool _dirty = true;
/// 是否在owner._dirtyElements中。它用于确定在该元素重新激活时是否应该将其添加回列表。
bool _inDirtyList = false;
/// BuildScope的dirty elements列表,只能由BuildOwner.buildScope调用重建,其参数就是标记为脏的element列表
BuildScope get buildScope => _parentBuildScope!;
BuildScope? _parentBuildScope;
getter方法
/// 获取当前位置上的RenderObject,
/// 如果当前节点不是RenderObjectElement,那么则沿着element tree往下找,直到找到或者没有子节点
/// 可能返回null:element尚未mount(如initiateState时)、非渲染行的Widget、element已经被unmount
/// RenderObect有什么作用呢?一般获取的RenderObject真正的类型是其子类RenderBox,可以用过它获取View的size和position
RenderObject? get renderObject {
Element? current = this;
while (current != null) {
if (current._lifecycleState == _ElementLifecycle.defunct) {
break;
} else if (current is RenderObjectElement) {
return current.renderObject;
} else {
current = current.renderObjectAttachingChild;
}
}
return null;
}
Element? get renderObjectAttachingChild {
Element? next;
visitChildren((Element child) {
next = child;
});
return next;
}
InheritedWidget相关的方法(BuildContext的接口的实现)
以下几个方法是获取共享数据、状态管理的核心方法,在使用Provider状态管理时,使用的原理就是这几个方法。
InheritedWidget dependOnInheritedElement(
InheritedElement ancestor, {
Object? aspect,
}) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget as InheritedWidget;
}
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({
Object? aspect,
}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedElements?[T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
}
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
return _inheritedElements?[T];
}
查找最近祖先Widget或State
T? findAncestorWidgetOfExactType<T extends Widget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != T) {
ancestor = ancestor._parent;
}
return ancestor?.widget as T?;
}
@override
T? findAncestorStateOfType<T extends State<StatefulWidget>>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
while (ancestor != null) {
if (ancestor is StatefulElement && ancestor.state is T) {
break;
}
ancestor = ancestor._parent;
}
final StatefulElement? statefulAncestor = ancestor as StatefulElement?;
return statefulAncestor?.state as T?;
}
@override
T? findRootAncestorStateOfType<T extends State<StatefulWidget>>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
StatefulElement? statefulAncestor;
while (ancestor != null) {
if (ancestor is StatefulElement && ancestor.state is T) {
statefulAncestor = ancestor;
}
ancestor = ancestor._parent;
}
return statefulAncestor?.state as T?;
}
@override
T? findAncestorRenderObjectOfType<T extends RenderObject>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element? ancestor = _parent;
while (ancestor != null) {
if (ancestor is RenderObjectElement && ancestor.renderObject is T) {
return ancestor.renderObject as T;
}
ancestor = ancestor._parent;
}
return null;
}
void didChangeDependencies() {
......
markNeedsBuild();
}
Element生命周期核心方法
/// 将element添加到给定的parent的slot中;
/// _lifecycleState由initial变为active
///
void mount(Element? parent, Object? newSlot) {
/// _lifecycleState == _ElementLifecycle.initial
/// _parent == null
/// slot == null
/// parent == null || parent._lifecycleState == _ElementLifecycle.active
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = 1 + (_parent?.depth ?? 0);
if (parent != null) {
_owner = parent.owner;
_parentBuildScope = parent.buildScope;
}
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
......
}
updateChild是Widget系统的核心方法,每次根据更新后的配置添加、更新或移除子项时,都会调用此方法。
| 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, Object? newSlot) {
if (newWidget == null) {
if (child != null) {
/// 1.如果 newWidget 为 null,child 不为 null,则移除 child,因为此时 child 的 widget 为null(即没有配置信息)
deactivateChild(child);
}
/// 2.如果都为 null,则什么都不做
/// 1和2 都返回 null
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
/// 3. 如果 child 不为 null,且 配置信息没有发生改变,则直接返回 child
newChild = child;
} else if (hasSameSuperclass &&
Widget.canUpdate(child.widget, newWidget)) {
/// 4. 如果 child 不为 null,且需要更新配置信息,则根据 newWidget 更新 child,然后返回 child
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
child.update(newWidget);
newChild = child;
} else {
/// 5.
/// 5.1 父类发生了改变 => stateful 和 statless之间切换
/// 5.2 配置信息发生了改变
/// 5.3 widget 的[runtimeType] 或 [key] 发生了改变
/// a. 将旧的 element 从树中移除
deactivateChild(child);
/// b. 创建新的 element
newChild = inflateWidget(newWidget, newSlot);
}
} else {
/// 6. child 为 null,newWidgte 不为 null,则根据新的配置信息创建新的 child 返回
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
根据给定的widget创建一个element,并将其作为child添加到指定的slot中。此方法通常由updateChild调用。
如果给定的 widget 存在 GlobalKey,并且global存在对应的 element,则该函数将会复用 element,而不是创建一个新的。element可能是树中其他位置的移植的,或者是从inactive列表中取的。
Element inflateWidget(Widget newWidget, Object? newSlot) {
try {
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
try {
newChild._activateWithParent(this, newSlot);
} catch (_) {
try {
deactivateChild(newChild);
} catch (_) {
......
}
rethrow;
}
final Element? updatedChild = updateChild(
newChild,
newWidget,
newSlot,
);
assert(newChild == updatedChild);
return updatedChild!;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
} finally {
......
}
}
void deactivateChild(Element child) {
assert(child._parent == this);
child._parent = null;
child.detachRenderObject();
owner!._inactiveElements.add(
child,
);
}
void deactivate() {
if (_dependencies?.isNotEmpty ?? false) {
for (final InheritedElement dependency in _dependencies!) {
dependency.removeDependent(this);
}
}
_inheritedElements = null;
_lifecycleState = _ElementLifecycle.inactive;
}
void activate() {
final bool hadDependencies =
(_dependencies?.isNotEmpty ?? false) || _hadUnsatisfiedDependencies;
_lifecycleState = _ElementLifecycle.active;
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
attachNotificationTree();
if (_dirty) {
owner!.scheduleBuildFor(this);
}
if (hadDependencies) {
didChangeDependencies();
}
}
void unmount() {
......
final Key? key = _widget?.key;
if (key is GlobalKey) {
owner!._unregisterGlobalKey(key, this);
}
_widget = null;
_dependencies = null;
_lifecycleState = _ElementLifecycle.defunct;
}
......
关于Element生命周期的方法,其子类会对其做做出修改,也会增加一些自己的方法,比如ComponentElement和RenderObjectElement,可以自行前往查看。