一 前言
阐明
本文主要叙述的是flutter的构建流程。一步步地去剖析Flutter是怎么从上往下构建Widget,趁便带出Flutter日常开发中的根本概念。
源码正告⚠️ 本文在剖析过程中会夹藏部分源码,全体边幅较长。友谊提示,经过导航栏去到相应的当地
问题
希望你能在阅读的过程能带着以下的一些问题。
- Widget,Element,RenderObject是什么,三者的联系是怎样的?
- build办法中的BuildContext是什么?
- setState发生了什么?
- 为什么GlobalKey能够使你在任意当地获取Widget?
正文分割线
二 Widget,Element,RenderObjct
Widget,Element,RenderObject阐明
在日常开发中,咱们经常触摸到都是Widget,包括各种的StalessWidget,StafulWidget,似乎各种Widget便是界面上的一个节点。可是,Widget并不是界面上的一个节点.它仅仅一个描绘节点的工具。请参照以下事例
其实依照咱们的开发经验可知。一个节点是无法一起出现在两个当地。就像原生开发中。一个按钮是无法一起添加到两个当地的。Widget便是一个描绘文件,担任发生一个节点,以及阐明这个节点的功能是什么。
那现在问题来了,究竟什么是节点呢?Flutter中的节点是一个比较神奇的存在。它不仅可用于表达UI界面上的一个元素,也能够用来表达一种布局信息,或是存储依靠联系等。
有一种说法是flutter中万物皆是Widget,其内在的意义便是flutter中的节点能够标明开发中的大部分的元素。flutter中的节点是用Element来标明。
既然说到节点Element能够表达大部分元素。那么详细到要发生在咱们能看到UI信息,是怎样做到的呢?
首要咱们先看看Widget类的界说
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key? key;
//创立节点Element
@protected
@factory
Element createElement();
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
...
}
Widget类是一个抽象类,其中有一个createElement()办法。这个办法是用来创立节点Element。至于创立什么类型的Element。详细由Widget子类去完结
然后咱们先看下Widget和Element这两个类的承继树
从图片能够很容易地调查出,每一种类型的Widget都有对应的Element。RenderObjectWidget是用来构建详细显现在界面上的节点的描绘信息类。在这儿咱们要点看下RenderObjectWidget和RenderObjectElement。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
//创立RenderObjectElement类型的节点
@override
@factory
RenderObjectElement createElement();
//创立RenderObject
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
在这儿,咱们能够看到RenderObjectWidget的createElement()办法会创立RenderObjectElement一个RenderObjectElement。然后看下RenderObjectElement的代码
abstract class RenderObjectElement extends Element {
RenderObjectElement(RenderObjectWidget widget) : super(widget);
@override
RenderObjectWidget get widget => super.widget as RenderObjectWidget;
@override
RenderObject get renderObject => _renderObject!;
RenderObject? _renderObject;
//mount办法极其重要,是一切Elemnt子类都必须需求完结办法
//便是经过这个办法一层层地往下构建Element节点树
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
#
_renderObject = widget.createRenderObject(this);
...
}
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
...
_dirty = false;
}
...
}
结合RenderObjectWidget和RenderObjectElement的源码,咱们能够看到这么一个基础的流程
能够看到Widget,Element和RenerObject三者的联系如下
值得一提的是,RenderObjct其实还不是真正显现在界面上的元素,其背面还有一个用于绘制的图层layer。联系有点像iOS开发中的UIView和CALayer。
这儿小结一下
- Element是Flutter中代表一个节点的类。这儿的节点不单单指UI上的界面元素
- Widget是Element的描绘类,用于阐明创立一个怎样的Element节点,
- RenderObjct是用于标明显现在UI上的布局和元素
三 Widget树构建
Widget树是什么?
咱们往常的学习过程中,经常会遇到Widget,Element和RenderObjct三棵树的概念,那究竟这是什么意思呢,三棵树究竟又是假如构建呢?结合前面三者的联系,咱们接着剖析一下。首要咱们先看下详细的demo。如下
图中左方是demo的效果,中间是demo的代码,右方是demo中的各个Widget。能够看到,右方的一切Widget以一个树状的方式层级打开。形象一点的话,如下
能够看到,Widget以一个树状的方式去一级级往下走。咱们在写Flutter项目的时分,一切的Widget都会以这样的一个树状层级构建,这个便是Widget树的概念。
Widget树怎样构建?
首要咱们从main函数开发剖析,我提炼一下要点,main函数履行后的流程如下
main函数终究会履行RenderObjectToWidgetAdapter类的attachToRenderTree办法。attachToRenderTree办法如下
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
//创立RenderObjectToWidgetElement节点
element = createElement();
assert(element != null);
//为节点设置一个BuildOwner
element!.assignOwner(owner);
});
//调用BuildOwner的buildScope办法
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
在构建过程中,这个办法做了两个工作,构建RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope办法。这个其实便是构建咱们Widget树的初步。
构建管理者BuilderOwner
你能够了解为BuilderOwner为一个Widget树的管理者,担任Widget树的管理和更新。他与Widget,Element和RenderObjct的联系大致如下
BuilderOwner不与Widget直接触摸,而是经过构建,更新Element树,从而构建和更新Widget树。
除了构建和更新Widget和Element树,BuilderOwner还担任管理GlobalKey等操作
构建流程
从前面咱们看到runApp()办法会调用到RenderObjectToWidgetAdapter的attachToRenderTree办法中。这个办法RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope办法。BuildOwner类和buildScope办法由于和更新过程联系比较大,稍后再详细描绘,这儿需求知道的是,buildScope会履行回调,也便是下面的代码
//调用BuildOwner的buildScope办法
//根据上方代码可知,element是RenderObjectToWidgetElement
owner.buildScope(element!, () {
element!.mount(null, null);
});
这儿的RenderObjectToWidgetElement其实是一个根节点,也便是Element树的根节点,从这个节点开端,咱们将一步步地往下构建子Element树.看下RenderObjectToWidgetElement中mount办法的完结
@override
void mount(Element? parent, Object? newSlot) {
...
super.mount(parent, newSlot);
_rebuild();
...
}
能够看到,这儿是调用了父类的mount办法,以及调用_rebuild办法。由于这一末节讲得是Widget树的构建。这儿要点关注一下super.mount办法,由于RenderObjectToWidgetElement承继RootRenderObjectElement,而RootRenderObjectElement承继RenderObjectElement。所以调用super.mount会调用到RenderObjectElement中的mount办法。如下
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
这儿做了一件事,便是为RenderObjectToWidgetElement绑定一个RenderObject,用于标明这个节点是需求显现在界面。然后履行完mount办法今后,会调用_rebuild()办法。如下
void _rebuild() {
try {
// 在这个事例中,widget.child 便是咱们在runApp办法中传入的MyApp()
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
...
}
_rebuild办法中会调用Element这个基类的updateChild办法。如下
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
//先使子节点child设置为一个不行用状况
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//假如子节点非空,
//则再判别是更新,删去子节点或是不对子节点进行操作
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
//widget和久的widget持平的状况下
//假如仅仅slot改动,则更新slot。不然widget无需更改
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//Widget更新
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
//其他状况,使子节点child设置为一个不行用状况,再重新构建一个子节点
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//假如子节点为空,则代表是子节点不存在,则为插入一个子节点
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
针对咱们runApp办法,其实是一次初次构建。那么这时分RenderObjectElement这时分是没有子节点。也便是说child为空。所以会直接走到newChild = inflateWidget(newWidget, newSlot) 这个办法里边,newWidget便是事例中的MyApp()这个实例 。inflateWidget如下
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
//Widget的key是否是GlobalKey,
final Element? newChild = _retakeInactiveElement(key, newWidget);
//找到本来的Widget对应的Element,假如在Element树的方位有改动,则更新方位
if (newChild != null) {
...
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
//调用newWidget,创立一个Element
final Element newChild = newWidget.createElement();
...
//调用mount办法,把newChild这个element插入到Element树对应的方位
newChild.mount(this, newSlot);
...
return newChild;
}
能够看到,上面的代码中调用了newWidget.createElement()。也便是MyApp().createElement()的办法。这时分,就终于调用到咱们自己写的代码了。从咱们的代码能够看出,MyApp承继StatelessWidget。
StatelessWidget的createElement办法会创立一个StatelessElement。所以上方代码中的newChild便是一个StatelessElement类型的Element。而StatelessElement承继ComponentElement。当调用mount办法时,会履行ComponentElement的mount办法。
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
这儿能够看到,履行了_firstBuild办法。__firstBuild办法终究会调用performRebuild办法。performRebuild如下
void performRebuild() {
...
built = build();
...
_child = updateChild(_child, built, slot);
...
}
这个办法做的工作,便是调用build()办法发生一个Widget。然后再调用updateChild办法,传入Widget,然后去创立,或是更新一个Element。
ComponentElement中的build()办法不做任何工作,交由子类去完结。那么关于方才的MyApp()这个Widget。对应的便是StatelessElement。StatelessElement的build办法如下。
Widget build() => widget.build(this);
这个办法就仅仅简略的回来widget.build(this)。可是,widget.build(this)这个办法是否似曾相识。没错,他其实便是咱们往常写StatelessWidget或是State时分的build办法。关于咱们的demo,便是MyApp的build办法,如下
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
留心一下,这儿的build办法的参数是BuildContext。结合咱们上面的流程可知,BuildContext其实便是Element。了解这一点非常的重要。
结合上面的剖析可知,咱们往常开发中的BuildContext其实便是父Element节点。经过这个父Element节点,咱们能够对节点树进行一些操作,详细看BuildContext的界说。
梳理一下咱们方才的剖析,能够大概得出以下这么一个流程。
- runApp()办法创立RenderObjectToWidgetAdapter,并传入MyApp这个Widget,作为RenderObjectToWidgetAdapter的child。
- RenderObjectToWidgetAdapter创立RenderObjectToWidgetElement。
- 调用RenderObjectToWidgetElement的mount办法
- mount办法中调用child或是children的build办法去创立子Widget
- 调用子Widget的createElement,发生一个子Element.
- 调用子Element的mount办法
然后不断的重复4~6这三个过程,不断的发生子Widget和子Element,直到Widget树和Element树完全构建为止。
构建流程图
结合上面的剖析以及不同类型的Widget和Element(详细能够看framework.dart完结,这儿不做详细剖析)。能够得出以下的一个构建流程
四 Widget树是怎样更新的?
State
咱们知道,一个界面不是原封不动的。当Widget和Element树构建完今后,咱们需求更新界面。在Flutter开发中,咱们一般会经过setState()办法去改写界面。那调用setState办法发生了什么工作呢?
setState()办法是State里边的一个办法。首要咱们先了解一下State是什么。要了解State,要先了解一下StatelessWidget和StatefulWidget。StatelessWidget和StatefulWidget都是承继于Widget。
StatelessWidget代表没有状况的Widget,也便是说这个Widget是不会改动的。StatefulWidget代表的是有状况的Widget,也便是这个Widget是会随着状况的改动。前面说到,Widget终归是仅仅一个描绘类。作用是用来发生节点的。所以咱们看下StatelesslElement和StatefulElement这两个类
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: state = widget.createState(),
super(widget) {
state._element = this;
state._widget = widget;
}
@override
Widget build() => state.build(this);
final State<StatefulWidget> state;
...
}
能够看到StatefulElement比StatelessWidget多了一个state。state的代码如下
abstract class State<T extends StatefulWidget> with Diagnosticable {
...
T get widget => _widget!;
T? _widget;
bool get mounted => _element != null;
...
BuildContext get context {
...
return _element!;
}
StatefulElement? _element;
...
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element.markNeedsBuild();
}
...
}
能够看到StatefuleElement,StatefuleWidget与State的联系如下
widget去创立一个Element,Element的创立时调用Widget的createState办法创立一个State。这个State就代表着Element的状况,它内部持有Widget和Element,能够一起访问到Widget和Element里的内容。
从最了解的办法,也便是State的setState办法开端讲起。setState办法界说如下
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element.markNeedsBuild();
}
能够看出,这个办法先是咱们调用了传入的办法,履行办法后,就调用了State中的_element的markNeedsBuild()办法,markNeedsBuild()办法是将一个Element符号为需求更新。办法的界说如下
void markNeedsBuild() {
...
if (!_active) return;
...
_dirty = true;
owner.scheduleBuildFor(this); //参加build owner的rebuild方案
}
markNeedsBuild办法首要判别一个Elemnt是否是_active,假如不是,则不做任何处理,由于一个非活跃的状况不会显现在界面上,所以不需做处理。然后将其_dirty值设置为true,符号这个Element 需求更新。然后调用BuildOwner的scheduleBuildFor办法,并传入需求更新的Element。BuildOwner的scheduleBuildFor界说如下
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled(); // 告诉Engine 在下一帧需求做更新操作;
}
_dirtyElements.add(element);
...
}
办法里的_scheduledFlushDirtyElements标明是否是在更新过程中,能够看到,这儿先判别_scheduledFlushDirtyElements是否为true,便是不在更新过程中,则调用onBuildScheduled告诉结构需求进行更新树。 然后将方才传入的element添加到BuildOwner的_dirtyElements中。这个_dirtyElements是一个列表,存储着需求一切更新的Elemnt。
当结构收到需求更新树的信息后,就会调用BuildOwner的buildScope()办法,前面在构建过程中说到过这个buildScope办法,可是没有细说,在这儿咱们看一下buildScope办法的完结
void buildScope(Element context, [VoidCallback callback]) {
if (callback == null && _dirtyElements.isEmpty) return;
...
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
...
callback(); //callback先履行
}
while (index < dirtyCount) {
...
_dirtyElements[index].rebuild(); //进行rebuild
...
}
_dirtyElements.clear();
}
复制代码
buildScope()办法中先是履行了callback办法,关于首次构建的流程,这个callback办法便是调用RootRenderObjectElement,也便是根Elemnt的mount办法进行构建。
然后这个办法主要就做了一件事,便是从_dirtyElements列表中取出每一个需求更新的Element,然后对一切Elemnt调用rebuild()办法,再清空_dirtyElements列表,标志着这一轮更新完结。
Element中的rebuild办法会调用performRebuild办法。performRebuild在Element中没有详细的完结。关于不同的Element子类,有着不一样的额外的完结。
首要关于RenderObjctElemnt及其子类,performRebuild只会调用updateRenderObject去更新RenderObjct目标,不会触及任何的子节点的更新.那这儿就有疑问了,该怎样对有子节点的RenderObjctElemnt进行更新子树呢?这儿先留一下小疑问,等讲完ComponetElemnt然后再回答。
关于ComponetElemnt及其子类,调用performRebuild办法会调用updateChild办法。前面在构建过程中,就贴出了updateChild这两个办法,这儿就不再重复去贴代码。在这儿要点说一下updateChild这个办法
updateChild
updateChild中传入了(Element child, Widget newWidget, dynamic newSlot) 三个参数。child代表的是该ComponetElemnt的子节点,newWidget是子节点对应的Widget,newSlot是子节点的方位信息
在履行updateChild过程中根据传入的参数做了以下的一些处理。
- 假如new widget为空,可是element不为空(也便是原有的widget被删去了)。首要deactivateChild(child),假如child不为空,则解绑child的renderobjce,并添加到build owner中的_inactiveElements列表中,并回来函数。deactivateChild(child会把child添加到BuildOwner_inactiveElements中)
- 假如new widget非空,child不为空
- 假如传入的widget和本来的widget是同一个,可是slot不一样,则调用updateSlotForChild更新方位
- 假如不是同一个widget,则判别widget的canUpdate是否是true,假如是true的话(代表element的能够重用),先判别slot是否和本来的slot持平,不持平,则调用updateSlotForChild更新方位。然后调用elemnt的update办法进行更新
- 假如不符合上面2.1,2.2的状况(则代表element不行重用,newWidget需求用例外一个新的element),则调用deactivateChild(child)办法,并调用inflateWidget(newWidget, newSlot)发生新的child
- newWidget为空,child为空。则标明该Wiget还不存在Element,则调用inflateWidget去创立一个新的Element。
上面的1,2,3别离代表着删去,修改,增加 Elemnt子节点的三种状况。当对一个element(ComponetElemnt及其子类的实例)调用markNeedsBuild办法的时分,会调用到updateChild办法去更新该element。
关于过程1,当一个Widget不再运用的时分,会调用deactivateChild办法,这个办法会把对应的Element放入到BuildlOwner的_inactiveElements列表中,假如Element被再次运用到(如运用了GlobalKey),就会从_inactiveElements列表中移除。假如没有被再次用到,再一次更新树的时分就会被销毁。
关于过程2.2,elemnt的update办法只会简略的设置widget。详细的完结由各个子类完结。这一步能够buid办法能够沿下更新树
如StatelessElement会调用rebuild办法,RenderObjectElemnt会更新renderobjct。像SingleChildRenderObject和MutilChildRederObjectElemnt等有子elemnt的还会调用updateChild办法(MutilChildRederObjectElemnt 是调用updateChildren,可是updateChildren是对updateChild的一个包装,对传入的列表逐一调用updateChild)更新子elemnt,
在说RenderObjctElemnt的performRebuild时分,留了一个小疑问:怎么更新有子节点的RenderObjcetElement的子树。
其实在开发过程中,咱们是不会直接调用RenderObjcetElement的markNeedsBuild办法的。就拿Stack这个Wigdet来说。Stack承继自MultiChildRenderObjectWidget。MultiChildRenderObjectWidget对应的是MutilChildRederObjectElemnt。
但会发现,Stack是没有setState办法的,也就说不会直接调用对应Element的markNeedsBuild办法。Stack一般是被ComponetWidget及其子类对应的Widget(如StatefuleWidget)所包裹着,也便是Stack的一般都有一个父Widget包裹着。更新的时分,就会调用父Widget对应Element的updateChild更新子树。
假定你要在更新流程中,保存Stack,只删去Stack里的一个子节点,会走到父Widget对应的Element的updateChild办法中,也便是上方的过程2.2。
过程2.2中调用了child.update(newWidget)。也便是Stack对应的Element的update办法
Stack对应的Element是MutilChildRederObjectElement,update办法如下
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
...
_children = updateChildren(_children, widget.children,
forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
}
能够看到,update办法调用了updateChildren办法,updateChildren这个办法会遍历子节点,对每一个子节点调用updateChild办法,从而完结RenderObjcetElement子树的更新。
验证这一个流程也很简略,在MutilChildRederObjectElement那里的update办法打个断点即可,如下。调查左方的调用栈即可得到详细的流程
更新流程图
综合上面的流程能够看出,调用State的setState办法,其实便是符号State对应的Element需求更新,经过调用markNeedsRebuild办法符号Element需求更新并告诉到Flutter结构需求更细。当结构更新该Element的时分,会调用updateChild办法向下递归更新子树,直到叶子节点为止
五 Widget,Eleemtn,RenderObject树的弥补
上方的三,四末节标题虽然是Widget的构建和更新。可是其实Widget的构建和更新其实都是依靠Element的。在实践内容中其实是Widget和Element树的更新。可是关于RenderObject树的构建没有太多的描绘。
假如对上方Element的构建流程了解今后,再结合以下的代码,你就知道RenderObjct树是怎样构建的。
RenderObject树的构建
首要看下RenderObjectElement的mount办法,如下
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
//创立RenderObjct
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
...
}
...
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
//找到父RenderObjct节点,并在父RenderObjct节点的字节点中参加该RenderObject节点
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(
renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement =
_findAncestorParentDataElement();
if (parentDataElement != null) _updateParentData(parentDataElement.widget);
}
首要在Element的构建过程中,去创立RenderObject。然后在经过Element树找到父节点,并经过_ancestorRenderObjectElement指向父节点。这样在Element树构建的过程中,RenderObject树也会不断的构建。
RenderObject树更新
看下RenderObjectElement的update办法,如下
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
...
widget.updateRenderObject(this, renderObject);
...
}
RenderObject树的更新也是依靠于Element树的更新。当Element更新的时分,会调用RenderObjectElement的update办法,然后调用对应Widget的updateRenderObject办法。
关于开发者而言,对RenderObject的操作根本都是经过Widget去完结,根本屏蔽了Element的完结。
六 拓展
Flutter中的Key
Flutter中有各式各样的Key,作用大致都是用来标志一个目标的。集成体系大致如下
我们能够直接看下注释就能够知道不同的Key是用来做什么的。这儿想要点说一下GlobalKey。很多人用来做跨Widget访问状况运用。
先看下它的界说
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
...
factory GlobalKey({String? debugLabel}) => LabeledGlobalKey<T>(debugLabel);
...
const GlobalKey.constructor() : super.empty();
Element? get _currentElement =>
WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];
...
BuildContext? get currentContext => _currentElement;
...
Widget? get currentWidget => _currentElement?.widget;
...
T? get currentState {
final Element? element = _currentElement;
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
final State state = statefulElement.state;
if (state is T) return state;
}
return null;
}
}
能够看到,GlobalKey是用于符号一个State的。一起它有currentContext,currentWidget,和currentState。答应你在Widget树上的任何一个节点获取到其持有的Widget,State,Context。那是怎么做到的呢?
当我在Widget中声明一个GlobalKey时,随着Widget树的构建,会调用Element树的mount办法
看下Element的mount办法,如下
void mount(Element? parent, Object? newSlot) {
...
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
...
_owner = parent.owner;
}
...
final Key? key = widget.key;
//是否是GlobalKey,假如是,向BuildOwner中注册一个GlobalKey
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
能够看到,假如一个Widget声明了一个GlobalKey,那么对应的Element就会在BuildOwner中调用_registerGlobalKey注册。BuildOwner的_registerGlobalKey如下
void _registerGlobalKey(GlobalKey key, Element element) {
...
_globalKeyRegistry[key] = element;
}
_globalKeyRegistry是BuildOwner的一个Map,用于存储一切的GlobalKey和其对应的Element.
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
当调用_registerGlobalKey的时分,实践上便是把GlobalKey和Element绑定在一起,放入到BuildOwner的一个Map当中。
前面说到过BuildOwner,BuildOwner担任Widegt树的构建和更新的,一般在Flutter的声明周期中,一般只会有一个BuildOwner实例(这一块在评论Flutter中的runApp流程的时分再评论)
当咱们去运用GlobalKey去拿Widget,Context,State的时分,其实便是以这个GlobalKey为键,去BuildOwner中取出对应的Element。再经过Element 获取到对应的Widget,State,Context。
便是那么的简略!!!
总结
其实了解Widget,Element,RenderObject树的概念不是必要的,可是很重要。关于后续了解app的流程,手势传递等,以及优化等剖析都是必不行少的部分。
这是一篇自己写过的文章,原文地址。为什么要重写一遍呢?一是本来的文章流程图上格式有些混乱,之前的流程图也丢失了,所以重新整理了一遍。二是之前的源码偏多,最近也正好重新看了下源码,所以换个思路再写一篇那样。