Flutter开发原理:三棵重要的树(排序流程、配置约束、应用视图构建)
DOM树的概念包含了页面上的所有控件。这些控件创建的自然嵌套关系使其看起来像“树”结构。我们也可以将这个概念应用到 Flutter 中。例如,默认计数器应用程序的配置如下所示:
我们还可以看到上图中每个控件组成的树形结构。与此有一定关系。例如,在上图中,我们可以说 Text 元素是 Column 元素的子组件,Scaffold 是 AppBar 的父元素。这种地理关系使得各个控件之间关联清晰,实现了树形结构。
我们也知道Container、Text等元素都属于Widget,所以在Flutter中这棵树称为Widget树,也可以称为控件树,它代表了控件的结构。 -eye我们在 dart 代码中编写了。
另外,在Flutter框架中,渲染元素在屏幕上的实际工作并不在控制层(Widget),而是在渲染层(Rending)。那么编码组件呢?那么通过渲染层渲染呢? Flutter 中又添加了两棵树,即 Element 树和 RenderingObject 树。什么是元素?我们可以将另一个抽象称为 Widget。读者可以把它想象成一个更实用的控件,因为手机屏幕上显示的控件并不是我们在代码中编写的widget。我们在代码中使用的元素如Container、Text等以及其他Properties就是我们要创建的元素的配置信息。当我们第一次调用 build()
方法并希望将这些元素显示在屏幕上时,Flutter 会生成相应的 Widget 控件。 Element,同样,Element会被放到对应的Element树中。在 Flutter 中,Widget 通过复用可以适应多种场景。元素是我们实际显示在屏幕上的元素。
Elements 和 Widgets 之间的另一个区别是 Widgets 是不可变的(固定的)。要更新,就需要修复它。如果要将状态变量与小部件关联起来,可以使用 StatefulWidget。 StatefulWidget 使用 StatefulWidget。 createState 方法创建一个 State 对象,将其扩展为 Element 并将其加入到树中;
这里,为了更深入地理解上面定义的含义,我们可以举一个更清晰的例子。 Widget作为前辈,将最近的策略安装,即确认信息写在纸上,并发送给Element管理员。看到详细的配置信息后,设备开始真正启动。我们还需要小心,大老板随时会改变战略布局,然后他不会从原来的信中改变而是重写它。现在,为了减少工作量,管理者需要将新计划与旧计划进行比较并做出相应的决策。状况更新。这也是Flutter框架的一大改进。下一步就在这里。 Element 也是一位不错的经理,但他当然不会包揽所有工作,因此他雇佣了另一个 RenderObject 工作人员来帮助他完成繁重的工作。
RenderObject 负责 Flutter 中渲染元素的工作。它还具有相应的RenderObject树,用于匹配渲染约束和元素之间的约束,也称为渲染树。
熟悉了Flutter中的上述三棵树后,相信读者能够清楚地理解元素解读的过程。这对我们将来学习共同元素确实有帮助。我们需要以不同的方式看待自己,我们也会对我们创造的模式和控制的未知奥秘有更深入的了解。
组件渲染流程简述
从上面我们知道,控件树中的每个控件都会实现一个RenderObject对象进行渲染,所有的RenderObject都会创建一个渲染树。 Flutter 渲染流程如下:
Flutter 渲染流程从用户输入开始。当收到用户输入时,将触发动画进度更新,例如第一次加载时的开始动画,或者当移动屏幕滚动时重用列表项时的移动动画。之后,您需要开始构建查看数据库。在这一步中,Flutter 创建了上面提到的三个视图树。
之后,将设置视图,计算每个部分的尺寸,然后绘制以创建每个视图的视觉数据。这部分工作是由RenderObject完成的。这里,Flutter中的安装流程可以如下图所示。上述打印树构建完成后,父对象会向下传递子显示约束信息,子对象会根据条件返回Size,它有自己的,并将Size数据上传。最后,父母要做的就是完成计划过程。
最后一步是“光栅化”。上一步得到的合成视图数据实际上是矢量定义数据。光栅化有助于从该数据创建逐像素数据。在Flutter中,光栅化过程是在Engine层实现的。
随着日常的开发和学习,我们只需要在代码层搭建好widget树,了解各种widget的特性和使用方法即可。其余的工作可以通过系统层来实现。
计数器应用订购流程简述
忙线陷阱
组件树详细说明
我们已经了解了各种控制功能以及如何使用它们。这些小部件由我们的开发人员设计,具有许多功能。定义其呈现样式,例如配置Text元素所需的字符串,配置输入框元素所需的内容。我们的Element树会记录这个配置信息。熟悉React的读者可能对“虚拟DOM”这个概念很熟悉,上面提到的Flutter函数也代表了这个概念。小部件不会改变。改变它意味着重建,而且经常是重建。如果我们给它更多的工作,就会造成性能上的很大损失,所以我们把widget元素当成虚拟元素树,但屏幕上实际显示的是Elememt树,它有对应widget的引用他。如果对应的 widget 发生变化,就会被标记为脏项,这样下次更新查看时,就会根据这个状态只更新变化的内容,提高性能。
每次将控件放置到控件树上时,Flutter 都会调用 createElement() 方法来创建相应的元素。然后 Flutter 将该组件放置在组件树中,并保存对创建它的控件的引用,如下所示:
该控件将具有以下子组件:
子线程还将创建相应的组件并安装它。元素树中:
Element
中的状态我们上面提到了widget的常量,相关元素都有自己的变量。正如我们之前所说,它被标记为脏元素,因为状态需要更新。另外我们要特别注意的是,与有状态元素(statefulWidget)相关的State对象实际上是由Element管理的,如下图所示。
Flutter 中的小部件正在不断改进。每次配置后,Element 将采取适当的步骤来确定相关的新控件是否与之前描述的旧控件相比发生了更改。如果没有改变,则只需执行更新操作即可。如果前后不同,就会重复。那么,Element 使用什么来判断控件是否发生了变化呢? Widgets 会比较以下两个属性:
- Element type
- Widget key(如果有)
Element type 表示前后控件由同一个类创建,并且 key 是唯一的。每个控件的标识。
实例证明Element保留了元素的属性
占坑
渲染树详细讲解
我们已经了解了Flutter中的三棵重要的树以及Element的运行原理。其中,第三个渲染树的任务就是做呈现元素的具体布局的任务。
渲染树中的每个节点都是继承自RenderObject类的对象,由Element中的renderObject或RenderObjectWidget中的createRenderObject方法生成。该对象提供了许多属性和方法来将元素添加到系统层。布局的翻译。
我们知道StatelessWidget和StatefulWidget是直接继承自Widget的两个类。在Flutter中,还有一个类RenderObjectWidget也直接继承自Widget。它没有构建方法。您可以使用createRenderObject直接创建一个RenderObject对象并将其放置在渲染树中。中间。 Column 和 Row 等控件是从 RenderObjectWidget 间接继承的。
主要属性和方法如下:
- 对象约束,来自其父对象的约束
- parentData对象,具有与其父对象相关的有用信息。
- performLayout,计算此渲染对象的布局。
- 绘画方法,勾勒出这个元素及其元素。
RenderObject 作为抽象类。每个节点都需要实现这一点以获得实际输出。扩展 RenderOject 的两个最重要的类是 RenderBox 和 RenderSliver。这两个类是分别实现Box协议和Sliver协议的所有渲染对象的父类。它们还扩展了数十个其他类,用于管理特定域并实现发布过程的细节。 ,比如RenderShiftedBox和RenderStack等
布局约束
上面介绍元素渲染过程时,我们了解到Flutter中的控件在绘制和渲染到屏幕上之前需要定位。这可以分为两个步骤:从上到下传递约束,从下到上传递布局信息。其流程如下图所示。
第一条线传递用于传递坐标约束。父节点向每个子节点发送约束。这些约束是每个子节点在部署过程中必须遵守的规则。正如父母对孩子所说:“你必须遵守校规,然后才能做其他事情”。常见的约束包括指定子框架的最大和最小宽度或者子框架的最大和最小高度。该约束将向下传播,子元素也会生成约束并将其传递给其子元素,一直传递到叶基。
第二个线性步骤用于传达特定于配置的信息。子节点接受父节点的约束后,会根据父节点的约束生成自定义的配置信息。例如,父节点指定最小宽度为 500 点。子节点可以根据这个规则将自己的宽度定义为500像素。或任何小于 500 像素的值。这样,在您定义您的个人信息后,请与您的父母共享此信息。父节点也将继续此活动一直到顶部。
下面我们将详细介绍可以传递给树的具体约束。 Flutter中主要有两种协议:Box协议和Sliver滑动协议。这里我们以box协议为例来详细介绍一下。
在box协议中,父节点传递给其子节点的约束是BoxConstraints。该约束指定每个子节点可以具有的最大和最小宽度和高度。如下图所示,父节点传入 BoxConstraints,最小宽度为 150,最大宽度为 300:
当接受这个约束时,这是子节点,我们可以得到绿色区域中的值上图。宽150到300之间,高100多。获取到具体值后,会将具体大小的值发送给父节点,从而实现父子关系。
修复中央控制
并稍后更新。您还可以查看每个组件的源代码以了解上述规则如何应用。
App视图配置
Flutter App的输入部分出现在以下代码中:
import 'package:flutter/material.dart';
// 这里的 MyApp是一个 Widget
void main() => runApp(new MyApp());
复制代码
runApp
该函数接受一个Widget类型对象作为参数,这意味着仅在Flutter概念中。有一个观点。所有其他逻辑仅服务于 View 的数据和状态更改,而不服务于 ViewController(或 Activity)。接下来,让我们看看 runApp
做了什么:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
new WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
复制代码
在 runApp
中,未来的小部件被放置在相机上。这个 WidgetsFlutterBinding
是一个单一组件,它使用 mixin 来使用框架内实现的其他绑定服务,例如行为、核心服务、布局、图形等。叫。从该方法的注释中我们可以看出,调用该方法会动态构建数据库。这样做的好处是 Flutter 依赖 Dart 的 MicroTask 来处理数据库创建任务。在这里,整个周期通过动态调用进行“预热”,以便在下一次 VSync 滴答时数据可用,而不是等待 MicroTask 的下一次滴答。
所以让我们看看 attachRootWidget
这个函数的作用:
void attachRootWidget(Widget rootWidget) {
_renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
复制代码
attachRootWidget
将小部件分配给
void attachRootWidget(Widget rootWidget) {
_renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
复制代码
attachRootWidget
附加小部件到
void attachRootWidget(Widget rootWidget) {
_renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
复制代码
从这部分可以验证Flutter应用程序是通过Widget、Element、RenderObject这三棵树结构来维护整个应用程序数据的。 作者:MeandniattachRootWidget 将小部件分配到♽ReWidTo‸这个桥,通过这个桥,组件被Create,并且可以同时持有Widget和RenderObject的引用。那么从上面我们知道接下来发生的是第一个数据集。
链接:https://juejin.im/post/5cce7042518825415234792d
来源:作者掘金。如需商业印刷,请联系作者以获得许可。非商业转载请注明来源。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。