大家好!我叫Mikhail Zotiev,我是Surf的Flutter开发人员。我和大多数使用Flutter的开发人员一样,大多数人都喜欢在它的帮助下创建美观,便捷的应用程序如此容易。只需很少的时间即可进入Flutter开发。我最近从事游戏开发,现在我已经完全转向Flutter上的跨平台移动开发。简单是什么?借助许多基本的小部件,您可以构建相当不错的用户界面。随着时间的流逝,当所使用的行李相当不错时,某种任务不太可能使您处于停顿状态:是不寻常的设计还是精美的动画。最有趣的是-您甚至可能不用考虑以下问题就可以使用它:“它怎么工作?”由于Flutter具有开源功能,因此我决定弄清楚幕后情况(在部队的Dart方面),并与您分享。
小部件
我们所有人都多次听到过框架开发团队的说法:“ Flutter中的一切都是小部件。” 让我们看看是否真的是这样。为此,我们转到Widget类(以下简称Widget),开始逐渐熟悉其内容。我们将在该类文档中阅读的第一件事:描述[元素]的配置。
事实证明,小部件本身只是对某些Element(以下简称-element)的描述。小部件是Flutter框架中的中心类层次结构。小部件是用户界面一部分的不变描述。可以将小部件放大为元素,以管理基础渲染树。
总而言之,短语“ Flutter中的所有内容都是小部件”是了解如何使用Flutter安排所有内容的最低级别。小部件是Flutter层次结构中的中心类。同时,围绕它的许多其他机制可以帮助框架完成其任务。因此,我们了解了更多事实:- 小部件-用户界面一部分的不变描述;
- 小部件与称为元素的某些高级视图相关联;
- 元素控制渲染树的某些实体。
您一定注意到了一件奇怪的事。用户界面和不变性很难很好地融合在一起,我什至可以说它们是完全不兼容的概念。但是,当出现有关Flutter世界的设备的更完整图片时,我们将返回到这一点,但是现在,我们将继续熟悉该小部件的文档。窗口小部件本身没有可变状态(它们的所有字段都必须为final)。
如果希望将可变状态与窗口小部件相关联,请考虑使用[StatefulWidget],该状态会在膨胀为一个元素并合并到树中时创建一个[State]对象(通过[StatefulWidget.createState])。
本段对第一段进行了补充:如果我们需要可变的配置,则使用特殊的State实体(以下称为state),它描述了此小部件的当前状态。但是,状态不与窗口小部件关联,而是与其基本表示形式关联。给定的小部件可以零次或多次包含在树中。特别地,给定的小部件可以多次放置在树中。每次将小部件放置在树中时,它都会膨胀为[Element],这意味着多次合并到树中的小部件将被多次膨胀。
相同的窗口小部件可以多次包含在窗口小部件树中,或者根本不包含。但是,每当窗口小部件树中包含窗口小部件时,就会将一个元素映射到该窗口小部件。因此,在此阶段,小部件几乎完成了,让我们总结一下:- 小部件-层次结构的中心类;
- 小部件是一些配置;
- 小部件-用户界面一部分的不变描述;
- 小部件与以某种方式控制渲染的元素相关联;
- 小部件的更改状态可以由某些实体描述,但它与小部件无关,但与代表此小部件的元素有关。
元件
从我们学到的问题开始,问题开始了:“这些因素支配着一切?” 进行相同操作-打开Element类的文档。树中特定位置的[Widget]的实例化。
元素是小部件在树中特定位置的某种表示形式。窗口小部件描述了如何配置子树,但是由于窗口小部件是不可变的,因此可以使用同一窗口小部件同时配置多个子树。[Element]表示使用小部件来配置树中的特定位置。随着时间的流逝,与给定元素关联的窗口小部件可以更改,例如,如果父窗口小部件为该位置重建并创建了一个新窗口小部件。
该小部件描述了用户界面的某些部分的配置,但是我们已经知道,同一小部件可以在树的不同位置使用。每个这样的地方将由相应的元素表示。但是随着时间的流逝,与该项目关联的窗口小部件可能会更改。这意味着元素更加顽强并且继续使用,仅更新它们的连接。这是一个相当合理的决定。正如我们在上面已经定义的那样,小部件是一个不变的配置,仅描述了接口的特定部分,这意味着它们必须非常轻巧。而且控制区域中的元素要沉重得多,但不必不必要地重新创建它们。要了解如何完成此操作,请考虑元素的生命周期:- Widget.createElement , .
- mount . .
- .
- , (, ), . runtimeType key, . , , .
- , , , , ( deactivate).
- , . , , (unmount), .
- 例如,当您在树中重新包含元素时,如果该元素或其祖先具有全局键,则将从非活动元素列表中将其删除,将调用activate方法,并且与此元素关联的渲染对象将再次嵌入渲染树中。这意味着该项目应再次出现在屏幕上。
在类声明中,我们看到该元素实现了BuildContext接口。BuildContext是控制小部件在小部件树中的位置的工具,如其文档所述。几乎完全符合商品说明。该接口用于避免对元素的直接操作,但同时允许访问必要的上下文方法。例如,findRenderObject,它允许您查找与此元素对应的渲染树对象。渲染对象
仍然需要处理此三元组的最后一个链接-RenderObject。顾名思义,这是可视化树的对象。它具有父对象,以及父对象用来存储有关该对象本身的特定信息(例如其位置)的数据字段。该对象负责基本渲染和布局协议的实现。RenderObject并不限制使用子对象的模型:可能不存在,一个或多个。同样,定位系统不限于:笛卡尔系统,极坐标,所有这些以及更多可供使用。使用位置协议没有任何限制:调整宽度或高度,限制大小,设置父对象的大小和位置,或者在必要时使用父对象的数据。颤抖的世界图片
让我们尝试对所有事物如何协同工作进行全面了解。上面我们已经提到,小部件是一个不变的描述,但是用户界面并不是完全静态的。通过将对象划分为3个级别和划分责任区,可以消除这种差异。
让我们用一个简单的示例看一下这些树的外观:
在这种情况下,我们将一些StatelessWidget包装在Padding小部件中并在其中包含文本。让我们代替Flutter-我们得到了这个小部件树。Flutter:“嘿,填充,我需要您的元素”Padding:“当然,握住SingleChildRenderObjectElement”
Flutter:“元素,这是您的位置,安顿下来”SingleChildRenderObjectElement:“伙计,一切正常,但我需要RenderObject”Flutter:“ Padding,喜欢完全吸引你?”填充:“保留它,RenderPadding”SingleChildRenderObjectElement:“很棒,开始工作”
Flutter:“那么下一个是谁?” StatelessWidget,现在让元素»StatelessWidget:«这里StatelessElement»Flutter:«StatelessElement,您将服从SingleChildRenderObjectElement,在这里着手进行»StatelessElement:«OK»
Flutter:«RichText,elementik存在,请»RichText给MultiChildRenderObjectElementFlutter:“ MultiChildRenderObjectElement,开始,开始”MultiChildRenderObjectElement:“我需要工作渲染”Flutter:“ RichText,我们需要渲染对象”RichText:“这里是RenderParagraph”Flutter:“ RenderParagraph您将收到RenderPadding指令,而MultiChildRenderObjectElement将控制您”MultiChildRenderObjectElement:“现在一切正常,我已经准备就绪”,
您肯定会问一个逻辑问题:“ StatelessWidget的渲染对象在哪里,为什么不在那里,我们在上面决定了元素绑定配置显示?” 让我们注意mount方法的基本实现,该方法在生命周期描述的这一部分中进行了讨论。void mount(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
assert(slot == null);
assert(depth == null);
assert(!_active);
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null)
_owner = parent.owner;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
assert(() {
_debugLifecycleState = _ElementLifecycle.active;
return true;
}());
}
我们不会在其中看到渲染对象的创建。但是该元素实现了BuildContext,该上下文具有findRenderObject可视化对象搜索方法,该方法将导致我们进入以下获取方法:RenderObject get renderObject {
RenderObject result;
void visit(Element element) {
assert(result == null);
if (element is RenderObjectElement)
result = element.renderObject;
else
element.visitChildren(visit);
}
visit(this);
return result;
}
在基本情况下,元素可能不会创建渲染对象;仅需要RenderObjectElement及其后代才能执行此操作,但是,在这种情况下,处于某个嵌套级别的元素必须具有包含渲染对象的子元素。看来为什么所有这些困难。多达3棵树,不同职责范围等答案很简单-这就是Flutter性能的基础。小部件是不可变的配置,因此,通常会重新创建它们,但是同时它们非常轻巧,这不会影响性能。但是Flutter正在尝试尽可能多地重用重元素。考虑一个例子。屏幕中间的文本。这种情况下的代码如下所示:body: Center(
child: Text(“Hello world!”)
),
在这种情况下,小部件树将如下所示:
Flutter构建了所有3棵树之后,我们得到以下图片:
如果更改将要显示的文本会发生什么?
现在,我们有了一个新的小部件树。上面我们讨论了元素的最大可能重用。看一看Widget类的方法,它的话语为canUpdate。static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
我们检查以前的小部件和新的小部件的类型,以及它们的键。如果它们相同,则无需更改项目。因此,升级之前,第一个元素是“中心”,升级之后,也是“中心”。两者都没有键,完全是巧合。我们可以将项目链接更新为新的小部件。
但是除了类型和键之外,小部件是描述和配置,显示所需的参数值可能会更改。这就是为什么元素在更新到小部件的链接之后,应该启动对渲染对象的更新。就Center而言,什么都没有改变,我们将继续进行比较。再次,类型和键告诉我们重新创建元素没有意义。文本是StatelessWidget的后代;它没有直接的显示对象。
转到RichText。该小部件也没有更改其类型;键中没有任何差异。该项目使用新的小部件更新其关联。
连接已更新,仅保留以更新属性。结果,RenderParagraph将显示新的文本值。
下一个图纸框架的时间到了,我们将看到我们期望的结果。由于这种工作,Flutter的高性能得以实现。上面的示例描述了微件结构本身未更改的情况。但是,如果结构发生变化会怎样?正如我们从生命周期描述中了解到的那样,Flutter当然将继续尝试最大限度地利用现有对象,但是将为所有新的小部件创建新元素,并在帧末尾删除旧的和更多不必要的小部件。让我们看几个例子。为了确保以上所述,我们使用Android Studio工具-Flutter Inspector。@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _isFirst ? first() : second(),
),
floatingActionButton: FloatingActionButton(
child: Text("Switch"),
onPressed: () {
setState(() {
_isFirst = !_isFirst;
});
},
),
);
}
Widget first() => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"test",
style: TextStyle(fontSize: 25),
),
SizedBox(
width: 5,
),
Icon(
Icons.error,
),
],
);
Widget second() => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"one more test",
style: TextStyle(fontSize: 25),
),
Padding(
padding: EdgeInsets.only(left: 5),
),
Icon(
Icons.error,
),
],
);
在这种情况下,通过单击按钮,将更改一个小部件。让我们看看检查员向我们展示了什么。
如我们所见,Flutter仅为Padding重新创建了渲染,其余的仅被重用了。再考虑1个选项,其中结构以更全局的方式更改-我们更改嵌套级别。Widget second() => Container(child: first(),);

尽管该树根本没有视觉上的变化,但仍重新创建了渲染树的元素和对象。发生这种情况是因为Flutter会按级别进行比较(在这种情况下,大多数树没有变化都没关系),因此在比较Container和Row时进行了这部分的筛选。但是,可以摆脱这种情况。这将对我们帮助GlobalKey。为Row添加这样的键。var _key = GlobalKey(debugLabel: "testLabel");
Widget first() => Row(
key: _key,
…
);

一旦我们告诉Flutter该零件可以重复使用,他便抓住了机会。结论
我们更加熟悉Flutter魔术,现在我们知道它不仅在小部件中。Flutter是一种协调良好的机制,具有自己的层次结构和职责范围,使用它不仅可以创建美观的应用程序,还可以创建高效的应用程序。当然,我们只检查了设备中很小但很小的一部分,因此我们将在以后的文章中继续分析框架内部工作的各个方面。我希望本文中的信息有助于理解Flutter的内部工作方式,并帮助您在开发过程中找到优雅而有效的解决方案。感谢您的关注!资源资源
Flutter “ Flutter如何渲染小部件”,作者:Andrew Fitz Gibbon,Matt Sullivan