电商网站的流程图,品牌网站制作简创网络,推广软件赚钱违法吗,桂林论坛网七星区1、framework源码组成
Flutter中widget、state、element的源码位于framework.dart中#xff0c;整个文件6693行(版本Flutter 3.12.0-14.0.pre.28)。整个代码可划分为若干部分#xff0c;主要包括key、widget、state、element四部分。
1.1 key
关于key的代码65行到272行整个文件6693行(版本Flutter 3.12.0-14.0.pre.28)。整个代码可划分为若干部分主要包括key、widget、state、element四部分。
1.1 key
关于key的代码65行到272行这里的key包括ObjectKey、GlobalKey、LabeledGlobalKey、GlobalObjectKey。整个key体系的代码还包括key.dart这个文件里面包括Key、LocalKey、UniqueKey和ValueKey。Key是GlobalKey和LocalKey的抽象基类。LabeledGlobalKey和GlobalObjectKey是GlobalKey的抽象子类。ObjectKey、UniqueKey和ValueKey是LocalKey的三个具体子类。
1.2 widget
关于widget的代码274行到1922行。包括10个抽象类Widget、StatelessWidget、StatefulWidget、ProxyWidget、ParentDataWidget、InheritedWidget、RenderObjectWidget、LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。这些类大体可以把Widget分为组合式、渲染、功能性三种。
1.3 state
State的代码在823附近。State是一个抽象类。State首先起着一个枢纽的作用它持有widget也持有element。从state里获取context只是简单返回持有的element。另一方面State对外提供了widget的生命周期initState、didUpdateWidget、reassemble、deactivate、activate、dispose、didChangeDependencies。这些生命周期方法是系统提供给我们的钩子。如果我们要主动发起渲染请求的话就要调用State提供给我们的setState方法。而build则是我们告诉系统如何渲染这个widget的地方。前者提供时机后者提供内容。
1.4 BuildContext
BuildContext是一个抽象类代码位于2129-2485行。Element实现了BuildContext。
1.5 BuildOwner
BuildOwner位于2511-3168行。
1.6 element
element相关的代码位于3259行到6597行之间。接近一半的代码量可以看出element是核心部分。 2 StatelessWidget和StatefulWidget
Widget是一个抽象类里面关键的三个东西
final Key? key;Element createElement();static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType newWidget.runtimeType oldWidget.key newWidget.key;}
我们要关注下canUpdate这个方法的实现决定我们能否复用这个widget是由这个widget的runtimeType和key决定的。runtimeType表明了widget的类型不同类型的widget是不能复用的。key是我们人为指定的一个值它可以在同类型widget之间产生个体差异以便我们或者渲染系统找到它。不设置的时候就是null这时候只比较runtimeType就行。
我们先来介绍widget中的组合式子类StatelessWidget和StatefulWidget。StatelessWidget创建的element是StatelessElement
overrideStatelessElement createElement() StatelessElement(this);
而StatefulWidget创建的是StatefulElement并且还能创建state
overrideStatefulElement createElement() StatefulElement(this);protectedfactoryState createState();
StatelessWidget和StatefulWidget仍然是抽象类需要我们子类化。根据源码我们发现StatefulWidget和StatelessWidget只负责创建对应element并不持有它。而Statefulwidget只负责创建state同样也并不持有它。
3 RenderObjectWidget
RenderObjectWidget有三个抽象子类LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。分别代表了没有孩子、只有一个孩子和有多个孩子的三种RenderObjectWidget。源码我们不再展开想必你也猜到了是一个啥都没一个有个child最后一个有个children属性罢了。
相比于前面的StatelessWidgetRenderObjectWidget返回自己独特的RenderObjectElement并且还多了一个RenderObject
RenderObject createRenderObject(BuildContext context);protectedvoid updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }protectedvoid didUnmountRenderObject(covariant RenderObject renderObject) { }
RenderObjectWidget我们后面在RenderObjectElement的performRebuild里讲到了它会调用updateRenderObject进行更新。这里我们无法展开讲updateRenderObject需要去看具体子类里的实现。
3.1 ColoredBox
我们以SingleChildRenderObjectWidget的一个具体子类ColoredBox为例
final Color color;overrideRenderObject createRenderObject(BuildContext context) {return _RenderColoredBox(color: color);}overridevoid updateRenderObject(BuildContext context, RenderObject renderObject) {(renderObject as _RenderColoredBox).color color;}
我们发现它创建的RenderObject是一个私有类_RenderColoredBox。而我们前面提到的updateRenderObject在这里只是设置一下新的颜色。
我们再转去看_RenderColoredBox的实现
class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {_RenderColoredBox({ required Color color }): _color color,super(behavior: HitTestBehavior.opaque);/// The fill color for this render object.////// This parameter must not be null.Color get color _color;Color _color;set color(Color value) {if (value _color) {return;}_color value;markNeedsPaint();}overridevoid paint(PaintingContext context, Offset offset) {// Its tempting to want to optimize out this drawRect() call if the// color is transparent (alpha0), but doing so would be incorrect. See// https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for// a good description of why.if (size Size.zero) {context.canvas.drawRect(offset size, Paint()..color color);}if (child ! null) {context.paintChild(child!, offset);}}
}
主要是根据颜色在canvas上绘制背景色和child。设置新颜色会引起markNeedsPaint。markNeedsPaint相关代码在RenderObject里。
3.2 markNeedsPaint
void markNeedsPaint() {if (_needsPaint) {return;}_needsPaint true;// If this was not previously a repaint boundary it will not have// a layer we can paint from.if (isRepaintBoundary _wasRepaintBoundary) {if (owner ! null) {owner!._nodesNeedingPaint.add(this);owner!.requestVisualUpdate();}} else if (parent is RenderObject) {parent!.markNeedsPaint();} else {// If we are the root of the render tree and not a repaint boundary// then we have to paint ourselves, since nobody else can paint us.// We dont add ourselves to _nodesNeedingPaint in this case,// because the root is always told to paint regardless.//// Trees rooted at a RenderView do not go through this// code path because RenderViews are repaint boundaries.if (owner ! null) {owner!.requestVisualUpdate();}}}
这里出现了新的OwnerPipelineOwner。markNeedsPaint标记自己需要重新绘制如果自己是绘制边界就把自己加入需要绘制的节点列表里。如果不是绘制边界就调用父节点的markNeedsPaint。这里只是简单标记和放入列表真正执行绘制的时机是在WidgetsBinding.drawFrame里的flushPaint
void drawFrame() {buildOwner!.buildScope(renderViewElement!); // 1.重新构建widgetsuper.drawFrame();//下面几个是在super.drawFrame()执行的pipelineOwner.flushLayout(); // 2.更新布局pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息pipelineOwner.flushPaint(); // 4.重绘if (sendFramesToEngine) {renderView.compositeFrame(); // 5. 上屏将绘制出的bit数据发送给GPU}
}
3.3 markNeedsLayout
上面的代码里我们也看到了布局是在这之前的flushLayout执行的。RenderBox源码里PipelineOwner通过markNeedsLayout标记、收集需要布局节点
void markNeedsLayout() {if (_needsLayout) {return;}if (_relayoutBoundary null) {_needsLayout true;if (parent ! null) {// _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.// Conservatively mark everything dirty until it reaches the closest// known relayout boundary.markParentNeedsLayout();}return;}if (_relayoutBoundary ! this) {markParentNeedsLayout();} else {_needsLayout true;if (owner ! null) {owner!._nodesNeedingLayout.add(this);owner!.requestVisualUpdate();}}}void markParentNeedsLayout() {assert(_debugCanPerformMutations);_needsLayout true;assert(this.parent ! null);final RenderObject parent this.parent!;if (!_doingThisLayoutWithCallback) {parent.markNeedsLayout();} else {assert(parent._debugDoingThisLayout);}assert(parent this.parent);}
我们发现布局标记和绘制标记的实现是类似的都需要标记自身都需要向上寻找布局或者绘制的边界。PipelineOwner最终对其调用performLayout和markNeedsPaint
void flushLayout() {try {while (_nodesNeedingLayout.isNotEmpty) {final ListRenderObject dirtyNodes _nodesNeedingLayout;_nodesNeedingLayout RenderObject[];dirtyNodes.sort((RenderObject a, RenderObject b) a.depth - b.depth);for (int i 0; i dirtyNodes.length; i) {if (_shouldMergeDirtyNodes) {_shouldMergeDirtyNodes false;if (_nodesNeedingLayout.isNotEmpty) {_nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));break;}}final RenderObject node dirtyNodes[i];if (node._needsLayout node.owner this) {node._layoutWithoutResize();}}// No need to merge dirty nodes generated from processing the last// relayout boundary back._shouldMergeDirtyNodes false;}for (final PipelineOwner child in _children) {child.flushLayout();}} finally {_shouldMergeDirtyNodes false;}}void _layoutWithoutResize() {RenderObject? debugPreviousActiveLayout;try {performLayout();markNeedsSemanticsUpdate();} catch (e, stack) {_reportException(performLayout, e, stack);}_needsLayout false;markNeedsPaint();}
performLayout这个方法在RenderBox里实现为空需要子类自行实现。
前面ColoredBox这个例子里我们在updateRenderObject里改变颜色并不会引起布局变化。现在我们找一个RenderPositionedBox的源码来看看。
3.3 RenderPositionedBox
RenderPositionedBox是Align使用的renderObject。我们看看它的updateRenderObject实现
void updateRenderObject(BuildContext context, RenderPositionedBox renderObject) {renderObject..alignment alignment..widthFactor widthFactor..heightFactor heightFactor..textDirection Directionality.maybeOf(context);}
再进到RenderPositionedBox的set alignment实现
set alignment(AlignmentGeometry value) {if (_alignment value) {return;}_alignment value;_markNeedResolution();}void _markNeedResolution() {_resolvedAlignment null;markNeedsLayout();}
我们发现设置新的alignment会引起markNeedsLayout的调用。
4 三个功能性widgetProxyWidget、ParentDataWidget、InheritedWidget
暂不展开
5 StatefulElement和StatelessElement
我们再跳到StatelessElement构造方法 class StatelessElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatelessElement(StatelessWidget super.widget);overrideWidget build() (widget as StatelessWidget).build(this);
}
StatelessElement自身可以通过build返回子element对应的widget。
而StatefulElement构造方法 /// 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;}
我们发现在创建element的时候会先调用widget的createState创建state并指向它然后state就伸出两只手一只手拉着widget另一只手拉着element。element里面有一个重要的方法 Widget build() state.build(this);
这里我们可以认为state build出来的是element持有的widget的“child”。事实上无论StatelessElement还是Statefulwidget它们都没child这个概念但是对应的element是有一个child的属性的。所以我们姑且这么看待它们的关系。这里把element传进去只是因为我们可能需要用到element树一些上下文信息。
3.1 setState
现在看看我们的老朋友state里的setState方法的实现 void setState(VoidCallback fn) {_element!.markNeedsBuild();}void markNeedsBuild() {if (dirty) {return;}_dirty true;owner!.scheduleBuildFor(this);}void scheduleBuildFor(Element element) {_dirtyElements.add(element);element._inDirtyList true;}void rebuild() {performRebuild();}void performRebuild() {_dirty false;}完整的流程如下 中间省略一些代码我们直接跳到performRebuild实现。对于基类Element的实现只是简单标记为dirty。Element分为渲染和组件两种类型前者与渲染相关后者用于组成其他element。
3.2 performRebuild
对于跟渲染相关的RenderObjectElement的performRebuild则需要更新它的renderObject
void _performRebuild() {(widget as RenderObjectWidget).updateRenderObject(this, renderObject);super.performRebuild(); // clears the dirty flag}
对于跟组件相关的ComponentElement的performRebuild实现 void performRebuild() {Widget? built;try {built build();} catch (e, stack) {} finally {// We delay marking the element as clean until after calling build() so// that attempts to markNeedsBuild() during build() will be ignored.super.performRebuild(); // clears the dirty flag}try {_child updateChild(_child, built, slot);} catch (e, stack) {_child updateChild(null, built, slot);}}
这里的核心是会调用build方法创建新的widget然后使用这个widget去更新child element。从前面的代码中我们可以看到statefulElement和statelessElement的build实现是有差异的。但是返回的widget其实都是它们的child对应的widget。在多个渲染周期child element会一直存在而需要更新时widget就会重新创建。更新后的Element设置为当前Element的child。至于怎么更新我们等下再讲。
ComponentElement是ProxyElement、StatefulElement和StatelessElement的父类。但是只有StatefulElement覆写了performRebuild。进一步来到StatefulElement的performRebuild实现
void performRebuild() {if (_didChangeDependencies) {state.didChangeDependencies();_didChangeDependencies false;}super.performRebuild();}
StatefulElement增加的任务是如果依赖发生了变化要触发state的didChangeDependencies方法。
3.3 updateChild
回到前文我们再来看Element的updateChild实现
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {// 如果newWidget为null而child不为null那么我们删除child返回null。if (newWidget null) {if (child ! null) {deactivateChild(child);}return null;}final Element newChild;if (child ! null) {// 两个widget相同位置不同更新位置。先更新位置然后返回child。这里比较的是hashCodeif (child.widget newWidget) {if (child.slot ! newSlot) {updateSlotForChild(child, newSlot);}newChild child;} else if (Widget.canUpdate(child.widget, newWidget)) {//两个widget不同但是可以复用。位置不同则先更新位置。然后用新widget更新elementif (child.slot ! newSlot) {updateSlotForChild(child, newSlot);}child.update(newWidget);newChild child;} else {// 如果无法更新复用那么删除原来的child然后创建一个新的Element并返回。deactivateChild(child);newChild inflateWidget(newWidget, newSlot);}} else {// 如果是初次创建那么创建一个新的Element并返回。newChild inflateWidget(newWidget, newSlot);}return newChild;}
这里关键的两个方法是update和inflateWidget。
3.4 update
对于不同类型的child的update方法是不一样的。基类Element只是用新的替换旧的而已:
void update(covariant Widget newWidget) {_widget newWidget;}
对于StatelessElement的update就是直接更换widget overridevoid update(StatelessWidget newWidget) {//直接更换widgetsuper.update(newWidget);assert(widget newWidget);rebuild(force: true);}
对于StatefulElement的update除了更换widget还要更换state指向的widget
void update(StatefulWidget newWidget) {super.update(newWidget);final StatefulWidget oldWidget state._widget!;state._widget widget as StatefulWidget;final Object? debugCheckForReturnedFuture state.didUpdateWidget(oldWidget) as dynamic;rebuild(force: true);}
最后都通过调用rebuild标记自身dirty。
对于SingleChildRenderObjectElement就是对它的child调用updateChild对于MultiChildRenderObjectElement就是对它的children调用updateChildren
void update(SingleChildRenderObjectWidget newWidget) {super.update(newWidget);_child updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);}void update(MultiChildRenderObjectWidget newWidget) {super.update(newWidget);final MultiChildRenderObjectWidget multiChildRenderObjectWidget widget as MultiChildRenderObjectWidget;_children updateChildren(_children, multiChildRenderObjectWidget.children, forgottenChildren: _forgottenChildren);_forgottenChildren.clear();}
而对于ProxyElement主要是更新widget和通知
overridevoid update(ProxyWidget newWidget) {final ProxyWidget oldWidget widget as ProxyWidget;//使用新的widget更新持有的widgetsuper.update(newWidget);//通知其他关联widget自己发生了变化updated(oldWidget);//标记dirtyrebuild(force: true);}protectedvoid updated(covariant ProxyWidget oldWidget) {notifyClients(oldWidget);}
3.5 updateChildren
updateChild前面我们已经提到了而对于updateChildren的实现
ListElement updateChildren(ListElement oldChildren, ListWidget newWidgets, { SetElement? forgottenChildren, ListObject?? slots }) {Element? replaceWithNullIfForgotten(Element child) {return forgottenChildren ! null forgottenChildren.contains(child) ? null : child;}Object? slotFor(int newChildIndex, Element? previousChild) {return slots ! null? slots[newChildIndex]: IndexedSlotElement?(newChildIndex, previousChild);}// This attempts to diff the new child list (newWidgets) with// the old child list (oldChildren), and produce a new list of elements to// be the new list of child elements of this element. The called of this// method is expected to update this render object accordingly.// The cases it tries to optimize for are:// - the old list is empty// - the lists are identical// - there is an insertion or removal of one or more widgets in// only one place in the list// If a widget with a key is in both lists, it will be synced.// Widgets without keys might be synced but there is no guarantee.// The general approach is to sync the entire new list backwards, as follows:// 1. Walk the lists from the top, syncing nodes, until you no longer have// matching nodes.// 2. Walk the lists from the bottom, without syncing nodes, until you no// longer have matching nodes. Well sync these nodes at the end. We// dont sync them now because we want to sync all the nodes in order// from beginning to end.// At this point we narrowed the old and new lists to the point// where the nodes no longer match.// 3. Walk the narrowed part of the old list to get the list of// keys and sync null with non-keyed items.// 4. Walk the narrowed part of the new list forwards:// * Sync non-keyed items with null// * Sync keyed items with the source if it exists, else with null.// 5. Walk the bottom of the list again, syncing the nodes.// 6. Sync null with any items in the list of keys that are still// mounted.int newChildrenTop 0;int oldChildrenTop 0;int newChildrenBottom newWidgets.length - 1;int oldChildrenBottom oldChildren.length - 1;final ListElement newChildren ListElement.filled(newWidgets.length, _NullElement.instance);Element? previousChild;// 从前往后依次对比相同的更新Element记录位置直到不相等时跳出循环。.while ((oldChildrenTop oldChildrenBottom) (newChildrenTop newChildrenBottom)) {final Element? oldChild replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);final Widget newWidget newWidgets[newChildrenTop];// 注意这里的canUpdate本例中在没有添加key时返回true。// 因此直接执行updateChild本循环结束返回newChildren。后面因条件不满足都在不执行。// 一旦添加key这里返回false不同之处就此开始。if (oldChild null || !Widget.canUpdate(oldChild.widget, newWidget)) {break;}final Element newChild updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;newChildren[newChildrenTop] newChild;previousChild newChild;newChildrenTop 1;oldChildrenTop 1;}// 从后往前依次对比记录位置直到不相等时跳出循环。while ((oldChildrenTop oldChildrenBottom) (newChildrenTop newChildrenBottom)) {final Element? oldChild replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);final Widget newWidget newWidgets[newChildrenBottom];if (oldChild null || !Widget.canUpdate(oldChild.widget, newWidget)) {break;}oldChildrenBottom - 1;newChildrenBottom - 1;}// 至此就可以得到新旧List中不同Weiget的范围。final bool haveOldChildren oldChildrenTop oldChildrenBottom;MapKey, Element? oldKeyedChildren;
// 如果存在中间范围扫描旧children获取所有的key与Element保存至oldKeyedChildren。if (haveOldChildren) {oldKeyedChildren Key, Element{};while (oldChildrenTop oldChildrenBottom) {final Element? oldChild replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);if (oldChild ! null) {if (oldChild.widget.key ! null) {oldKeyedChildren[oldChild.widget.key!] oldChild;} else {deactivateChild(oldChild);}}oldChildrenTop 1;}}// 更新中间不同的部分如果新旧key相同就更新一下重新利用否则新的widget就没有旧的对应是插入行为while (newChildrenTop newChildrenBottom) {Element? oldChild;final Widget newWidget newWidgets[newChildrenTop];if (haveOldChildren) {final Key? key newWidget.key;if (key ! null) {// key不为null通过key获取对应的旧ElementoldChild oldKeyedChildren![key];if (oldChild ! null) {if (Widget.canUpdate(oldChild.widget, newWidget)) {// we found a match!// remove it from oldKeyedChildren so we dont unsync it lateroldKeyedChildren.remove(key);} else {// Not a match, lets pretend we didnt see it for now.oldChild null;}}}}final Element newChild updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;newChildren[newChildrenTop] newChild;previousChild newChild;newChildrenTop 1;}// Weve scanned the whole list.// 重置newChildrenBottom newWidgets.length - 1;oldChildrenBottom oldChildren.length - 1;// 将后面相同的Element更新后添加到newChildren至此形成新的完整的children。while ((oldChildrenTop oldChildrenBottom) (newChildrenTop newChildrenBottom)) {final Element oldChild oldChildren[oldChildrenTop];final Widget newWidget newWidgets[newChildrenTop];final Element newChild updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;newChildren[newChildrenTop] newChild;previousChild newChild;newChildrenTop 1;oldChildrenTop 1;}// 清除旧列表中多余的带key的Elementif (haveOldChildren oldKeyedChildren!.isNotEmpty) {for (final Element oldChild in oldKeyedChildren.values) {if (forgottenChildren null || !forgottenChildren.contains(oldChild)) {deactivateChild(oldChild);}}}return newChildren;}dif算法相对比较复杂可能理解起来比较困难。值得一提的是无论 updateChild还是updateChildren都实现在基类element里。同层diff算法里使用key并不是出于性能考虑没有key能够就地复用使用key能够指定复用对象。有时候就地复用会有一些问题譬如某个widget自身有一些状态你如果就地复用其他widget就会导致这些状态的丢失。
3.6 inflateWidget
再来看看inflateWidget的实现它主要是用来创建新的element并且mount。如果widget有GlobalKey的话则会尝试获取对应的element然后更新后返回。
Element inflateWidget(Widget newWidget, Object? newSlot) {try {//如果widget带key并且是GlobalKey则尝试获取一下对应的element并用新的widget更新它然后返回final Key? key newWidget.key;if (key is GlobalKey) {final Element? newChild _retakeInactiveElement(key, newWidget);if (newChild ! null) {newChild._activateWithParent(this, newSlot);final Element? updatedChild updateChild(newChild, newWidget, newSlot);return updatedChild!;}}// 这里就调用到了createElement重新创建了Elementfinal Element newChild newWidget.createElement();newChild.mount(this, newSlot);return newChild;} }3.7 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) {// Only assign ownership if the parent is non-null. If parent is null// (the root node), the owner should have already been assigned.// See RootRenderObjectElement.assignOwner()._owner parent.owner;}final Key? key widget.key;if (key is GlobalKey) {owner!._registerGlobalKey(key, this);}_updateInheritance();attachNotificationTree();}
mount就是将自身插入父element的某个slot中。我们发现Element在mount的时候会将父element的ower设置给自己。如果widget带有key那么ower会将这个element注册到自己的map里。
而对于组合式Element的mount有所差异除了上述基类行为还会调用_firstBuild
overridevoid mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);_firstBuild();}void _firstBuild() {// StatefulElement overrides this to also call state.didChangeDependencies.rebuild(); // This eventually calls performRebuild.}
对于StatelessElement_firstBuild的实现只是单纯rebuild一下。而对于StatefulElement:
overridevoid _firstBuild() {final Object? debugCheckForReturnedFuture state.initState() as dynamic;state.didChangeDependencies();super._firstBuild();}
我们发现_firstBuild里调用了state的initState方法这里说明我们在state里实现的生命周期方法其实会被StatefulElement根据自身的不同状态而调用。因此其他方法我们不再赘述。
3.8 why?
在参考文章里有一个问题我们来分析一下增加我们对本文的理解程度。现在我们有如下一段代码
import dart:math;import package:flutter/foundation.dart;
import package:flutter/material.dart;void main() runApp(MyApp());class MyApp extends StatelessWidget {overrideWidget build(BuildContext context) {return MaterialApp(title: Flutter Demo,theme: ThemeData(primarySwatch: Colors.blue,),home: MyHomePage(title: Home Page),);}
}class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);final String title;override_MyHomePageState createState() _MyHomePageState();
}class _MyHomePageState extends StateMyHomePage {ListWidget widgets;overridevoid initState() {super.initState();widgets [StatelessColorfulTile(),StatelessColorfulTile()];}overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Row(children: widgets,),floatingActionButton: FloatingActionButton(child: Icon(Icons.refresh),onPressed: _swapTile,),);}_swapTile() {setState(() {widgets.insert(1, widgets.removeAt(0));});}
}class StatelessColorfulTile extends StatelessWidget {final Color _color Utils.randomColor();overrideWidget build(BuildContext context) {return Container(height: 150,width: 150,color: _color,);}
}class Utils {static Color randomColor() {var red Random.secure().nextInt(255);var greed Random.secure().nextInt(255);var blue Random.secure().nextInt(255);return Color.fromARGB(255, red, greed, blue);}
}代码可以直接复制到DartPad中运行查看效果。 或者点击这里直接运行。
效果很简单就是两个彩色方块点击右下角的按钮后交换两个方块的位置。上面的方块是StatelessWidget那我们把它换成StatefulWidget呢。
class StatefulColorfulTile extends StatefulWidget {StatefulColorfulTile({Key key}) : super(key: key);overrideStatefulColorfulTileState createState() StatefulColorfulTileState();
}class StatefulColorfulTileState extends StateStatefulColorfulTile {final Color _color Utils.randomColor();overrideWidget build(BuildContext context) {return Container(height: 150,width: 150,color: _color,);}
}再次执行代码发现方块没有“交换”。这是为什么结论是widget层面而言两个widget的确发生了交换但是Element并没有发生交换原来位置的Element持有的state build出原来颜色的Container。 6 key
可以看参考这里暂不展开 7 BuildOwner
buildOwner是framework这些代码背后的大boss。我们来看看它做了哪些事情。每个element都指向一个Owner用来维护它的生命周期
BuildOwner? get owner _owner;
BuildOwner? _owner;
为什么我们能用globalKey找到对应的element没有什么神奇的因为buildOwner有一个map维护着globalKey和element的对应关系 final MapGlobalKey, Element _globalKeyRegistry GlobalKey, Element{};void _registerGlobalKey(GlobalKey key, Element element)
void _unregisterGlobalKey(GlobalKey key, Element element)
buildOwner另一个作用是维护着element的build列表 final ListElement _dirtyElements Element[];void scheduleBuildFor(Element element) {if (element._inDirtyList) {_dirtyElementsNeedsResorting true;return;}if (!_scheduledFlushDirtyElements onBuildScheduled ! null) {_scheduledFlushDirtyElements true;onBuildScheduled!();}_dirtyElements.add(element);element._inDirtyList true;}
WidgetsBinding会通过WidgetsBinding.drawFrame调用buildOwner的buildScope
void drawFrame() {buildOwner!.buildScope(renderViewElement!); // 1.重新构建widgetsuper.drawFrame();//下面几个是在super.drawFrame()执行的pipelineOwner.flushLayout(); // 2.更新布局pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息pipelineOwner.flushPaint(); // 4.重绘if (sendFramesToEngine) {renderView.compositeFrame(); // 5. 上屏将绘制出的bit数据发送给GPU}
}
buildScope对_dirtyElements里的element调用rebuild
void buildScope(Element context, [ VoidCallback? callback ]) {if (callback null _dirtyElements.isEmpty) {return;}try {_scheduledFlushDirtyElements true;if (callback ! null) {_dirtyElementsNeedsResorting false;try {callback();}}_dirtyElements.sort(Element._sort);_dirtyElementsNeedsResorting false;int dirtyCount _dirtyElements.length;int index 0;while (index dirtyCount) {final Element element _dirtyElements[index];try {element.rebuild();} index 1;if (dirtyCount _dirtyElements.length || _dirtyElementsNeedsResorting!) {_dirtyElements.sort(Element._sort);_dirtyElementsNeedsResorting false;dirtyCount _dirtyElements.length;while (index 0 _dirtyElements[index - 1].dirty) {// It is possible for previously dirty but inactive widgets to move right in the list.// We therefore have to move the index left in the list to account for this.// We dont know how many could have moved. However, we do know that the only possible// change to the list is that nodes that were previously to the left of the index have// now moved to be to the right of the right-most cleaned node, and we do know that// all the clean nodes were to the left of the index. So we move the index left// until just after the right-most clean node.index - 1;}}}} finally {for (final Element element in _dirtyElements) {assert(element._inDirtyList);element._inDirtyList false;}_dirtyElements.clear();_scheduledFlushDirtyElements false;_dirtyElementsNeedsResorting null;}}
后面的流程就回到了我们前面的performRebuild方法 。
8 没有覆盖的内容
本文没有提及具体的布局逻辑将在后面的文章里进行讲述。
9 图示
文中出现的一些关键类的继承关系 参考
1.说说Flutter中最熟悉的陌生人 —— Key_flutter globalkey 源码_唯鹿的博客-CSDN博客