本文由 简悦 SimpRead 转码, 原文地址 www.raywenderlich.com
潜心研究widget背后的理论。更好地了解小部件是如何被渲染的,如何......。
你可能听说过Flutter中的所有东西都是一个小部件。虽然这可能不是绝对正确的,但在你构建应用程序时,大多数时候你只看到顶层:widget。在本章中,你将深入了解widget理论。你将探索。
- 小部件
- 小部件的渲染
- Flutter检查器
- 小部件的类型
- 小工具的生命周期
是时候跳进去了!
注意:这一章主要是理论性的。在本章接近尾声时,您将对项目做一些代码修改。
什么是widget?
小组件是你的用户界面的一个构件。使用widget就像组合乐高积木一样。就像乐高积木一样,你可以混合和匹配widget来创造一些惊人的东西。
Flutter 的声明性特点使得用小组件构建用户界面变得非常容易。小组件是显示您的应用程序状态的蓝图。
你可以把widget看成是UI的一个功能。给定一个状态,widget的build()方法会构建widget的UI。
打开Card2的盒子
在上一章中,你创建了三个配方卡。现在,你将更详细地看看组成Card2的部件。
你还记得你需要哪些小部件来建立这个卡片吗?
回忆一下,这个卡片由以下几个部分组成。
- 容器小部件。样式、装饰和定位部件。
- 柱状小部件。垂直地显示其他部件。
- AuthorCard自定义小组件。显示作者的信息。
- 扩大的小组件。使用一个小部件来填补剩余的空间。
- 堆栈小组件。将小组件放置在彼此的顶部。
- 定位的小组件。控制一个小部件在堆栈中的位置。
小组件树
每个widget都包含一个build()方法。在这个方法中,你通过将小部件嵌套在其他小部件中来创建一个UI组合。这形成了一个树状的数据结构。每个小部件都可以包含其他小部件,通常称为子部件。下面是Card2的widget树的一个可视化图。
你也可以细分为AuthorCard和Expanded。
widget树提供了一个蓝图,描述了你要如何布局你的UI。框架会遍历树上的节点,并调用每个build()方法来组成你的整个UI。
渲染部件
在第1章 "入门 "中,您了解到Flutter的架构包含三个层次。
在本章中,您将关注框架层。你可以把这一层分成四个部分。
- Material和Cupertino是建立在widget层之上的UI控制库。它们分别使你的UI看起来和感觉像Android和iOS应用程序。 小部件层是小部件上的一个组合抽象。它包含了创建UI控件所需的所有原始类。在这里查看官方文档:api.flutter.dev/flutter/wid…
- 渲染层是一个布局抽象,用于绘制和处理widget的布局。想象一下,你必须手动重新计算每个小部件的坐标和框架。呸!"。
- Foundation,也被称为dart:ui层,包含处理动画、绘画和手势的核心库。
三棵树
Flutter的框架实际上不是在管理一棵树,而是在平行管理三棵树。
- 小工具树
- 元素树
- 渲染对象树
下面是一个单一的Widget在引擎盖下的工作方式。
- 小工具。框架的公共API或蓝图。开发人员通常只是处理组成Widget的问题。
- 元素。管理一个小部件和一个小部件的渲染对象。对于树上的每个widget实例,都有一个相应的元素。
- 渲染对象(RenderObject)。负责绘制和布置一个特定的widget实例。还负责处理用户交互,比如点击测试和手势。
元素的类型
有两种类型的元素。
- 组件元素。一种由其他元素组成的元素类型。这对应于在其他小部件内部组成小部件。
- RenderObjectElement。一种持有渲染对象的元素类型。
你可以把ComponentElement看作是一组元素,而RenderObjectElement是一个单一的元素。请记住,每个元素都包含一个渲染对象,以执行widget绘画、布局和点击测试。
Card2的例子树
下面的图片显示了Card2 UI的三个树的例子。
正如你在前几章看到的,Flutter通过调用runApp()开始构建你的应用程序。每个widget的build()方法然后组成一个widget的子树。对于widget树中的每个widget,Flutter创建了一个相应的元素。
元素树管理每个widget实例,并关联一个渲染对象来告诉框架如何渲染一个特定的widget。
注:关于更多细节,请查看YouTube上的Mahogany staircase - Flutter分层设计,Ian Hickson在那里雄辩地介绍了Flutter部件在引擎盖下的工作原理。youtu.be/dkyY9WCGMi0
开始使用
在Android Studio中打开启动项目,必要时运行flutter pub get,然后运行该应用。你会看到前一章中的Fooderlich应用。
接下来,通过点击蓝色的Dart图标打开DevTools,如下图所示。
DevTools将在你的浏览器中打开。
注意:它在谷歌Chrome网络浏览器中效果最好。点击⚙图标,在黑暗和光明模式之间进行切换!
DevTools概述
DevTools提供了各种很棒的工具来帮助您调试您的Flutter应用程序。这些工具包括
- Flutter 检查器。用于探索和调试widget树。
- 性能。允许您分析Flutter帧图、时间线事件和CPU分析器。
- CPU 剖析器。允许您记录和剖析您的Flutter应用程序会话。
- 内存。显示Dart中的对象是如何分配的,这有助于发现内存泄漏。
- 调试器。支持断点和调用栈上的变量检查。还允许您在DevTools中直接浏览代码。
- 网络。允许您检查Flutter应用程序中的HTTP、HTTPS和网络套接字流量。
- 记录。显示在Dart运行时间上发生的事件和应用程序级别的日志事件。
- 应用程序大小:帮助您分析您的应用程序总大小。
有许多不同的工具可以使用,但在本章中,您将只看Flutter检查器。关于其他工具如何工作的信息,请查看:flutter.dev/docs/develo…
Flutter检查器
Flutter检查器有四个关键好处。它可以帮助您。
- 可视化您的部件树。
- 检查树中一个特定部件的属性。
- 使用布局浏览器试验不同的布局配置。
- 启用慢速动画以显示您的转换效果。
Flutter检查器工具
以下是使用 Flutter Inspector 的一些重要工具。
- 选择小部件模式。启用后,这允许您在设备或模拟器上点击一个特定的小部件来检查其属性。
点击小部件树中的任何元素也会突出显示设备上的小部件,并跳转到确切的代码行。这多酷啊!
- 刷新树。简单地重新加载当前小部件的信息。
- 慢速动画。放慢动画的速度,这样你就可以直观地检查用户界面的转换。
- 调试画。显示可视化的调试提示。这让你可以检查你的小部件的边框、镶边和对齐。
下面是它在设备上的截图。
- 画出基线。当启用时,这告诉RenderBox在每个文本的基线下画一条线。
在这里,你可以看到每个文本小组件的基线下的绿色线条。
- 重绘彩虹。每次Flutter重绘小部件时都会给它添加一个随机的边框。
如果您觉得无聊,您可以通过启用迪斯科模式来调剂一下,如下图所示。
- 颠倒过大的图像。告诉你在你的应用程序中哪些图像是超大的。
检查小部件树
在模拟器中,选择第一个标签,然后点击DevTools中的Refresh Tree。最后,选择Card1并点击Details Tree标签,如下图所示。
请注意。
- 在左边的面板中,有一部分Flutter小部件树正在调查,从根部开始。
- 当你点击树中的一个特定的部件,你可以检查它的子树,如右面板上的细节树标签所示。
- 细节树代表了元素树,并显示了组成该部件的所有重要属性。注意,它引用了renderObject。
细节树是一个很好的方法,可以让你检查和试验一个特定的小组件属性是如何工作的。
点击一个文本小组件,你会看到你可以配置的所有属性。
这有多有用?您可以检查所有的属性,如果有什么不明白的地方,您可以调出Flutter widget文档来阅读更多关于该属性的内容
布局资源管理器
接下来,点击Layout Explorer标签,如下图所示。
你可以使用布局资源管理器来可视化你的文本小部件在堆栈中的布局。
接下来,请遵循这些指示。
- 确保你的设备正在运行,并且DevTools在你的浏览器中打开。
- 点击底部导航栏中的Card3。
- 点击 "刷新树 "按钮。
- 选择树中的Column元素。
- 点击Layout Explorer。
你会看到下面的情况。
布局资源管理器对于实时修改灵活的小组件布局很方便。
该探索器支持修改。
- mainAxisAlignment
- 交叉轴对齐(crossAxisAlignment
- 灵活性
在主轴内点击开始,将数值改为结束。注意,配方趋势文本现在在卡片的底部。
当你需要在运行时检查和调整布局时,这很有用。请随意试验和玩弄布局浏览器。你可以创建简单的列或行小部件来乱用布局轴。
你现在拥有了调试部件所需的所有工具 在下一节中,你将了解到小部件的类型以及何时使用它们。
小组件的类型
有两种主要的小组件类型:无状态和有状态小组件。接下来你会仔细看看它们之间的区别。
无状态小组件
无状态小组件是不可改变的。一旦建立,你就不能改变这个小组件的状态或属性。当你的属性不需要随时间变化时,一般来说,用无状态小组件开始是个好主意。
无状态小组件的生命周期始于一个构造函数,你可以向其传递参数,以及一个build()方法,你可以重写该方法。小组件的视觉描述是由build()方法决定的。
以下事件会触发这种小组件的更新。
- 小组件被 插入到小组件树中。
- 依赖或继承的widget的状态--祖先节点--发生变化。
有状态的小组件
有状态的小组件保留状态,当你的用户界面的一部分需要动态变化时,这很有用。
例如,使用有状态的部件的一个好时机是当用户点击一个喜爱的按钮来切换一个简单的布尔值。
有状态的部件将它们的可变状态存储在一个单独的状态类中。这就是为什么每个有状态的部件必须覆盖和实现createState()。
接下来,看看有状态部件的生命周期。
状态对象的生命周期
每个widget的build()方法都需要一个BuildContext作为参数。构建上下文告诉你你在widget树中的位置。你可以通过BuildContext访问任何widget的元素。稍后,你会看到为什么构建上下文很重要,特别是对于从父部件访问状态信息。
现在,仔细看一下生命周期。
- 当你把构建上下文分配给widget时,一个内部标志, mounted,被设置为true。这让框架知道这个小组件目前在小组件树上。
- initState()是一个widget被创建后调用的第一个方法。这类似于Android的onCreate()或iOS的viewDidLoad()。
- 框架第一次构建一个widget时,会在initState()之后调用didChangeDependencies()。如果你的状态对象依赖于一个已经改变的继承部件,它可能会再次调用didChangeDependencies()。下面会有更多关于继承部件的内容。
- 最后,框架会在didChangeDependencies()之后调用build()。这个函数对开发者来说是最重要的,因为每次有widget需要渲染的时候,它都会被调用。树上的每个部件都会递归地触发build()方法,所以这个操作必须非常快。
注意:你应该总是异步地执行繁重的计算功能,并将其结果存储为状态的一部分,供build()使用。build()不应该做任何有计算要求的事情。这类似于你对iOS或Android主线程的看法。例如,你不应该做一个网络调用,使UI渲染停滞。
- 当父部件做出改变或需要重新绘制用户界面时,框架会调用didUpdateWidget(_)。当这种情况发生时,你会得到oldWidget实例作为参数,这样你就可以把它和你当前的widget进行比较,并做任何额外的逻辑。
- 每当你想修改你的widget的状态时,你就调用setState()。然后框架会将该widget标记为dirty并再次触发build()。
- 当你从树上删除对象时,框架会调用deactivate()。在某些情况下,框架可以将状态对象重新插入到树的另一部分。
- 当你永久性地从树上删除对象及其状态时,框架会调用dispose()。这个方法非常重要,因为你需要它来处理内存清理,比如取消订阅流和处置动画或控制器。
dispose()的经验法则是检查你在状态中定义的任何属性,确保你已经正确地处置了它们。
添加有状态的小部件
Card2目前是一个无状态Widget。注意到右上方的Heart按钮目前没有任何作用。这并不是因为你没有连接任何动作。这是因为这个小组件,就其本身而言,不能动态地管理状态。为了解决这个问题,你要把这个卡片变成一个StatefulWidget。
AuthorCard是嵌套在Card2中的。打开author_card.dart,右键点击AuthorCard。然后在弹出的菜单中点击显示上下文动作。
点击转换为StatefulWidget。与其手动转换,你可以直接使用这个菜单快捷方式来自动完成。
现在有两个类了。
class AuthorCard extends StatefulWidget {
...
@override
_AuthorCardState createState() => _AuthorCardState();
}
class _AuthorCardState extends State<AuthorCard> {
@override
Widget build(BuildContext context) {
...
}
在上面的代码中,有几件事需要注意。
- 这个重构将AuthorCard从一个无状态的Widget转换成一个有状态的Widget。它添加了一个createState()实现。
- 重构还创建了_AuthorCardState状态类。它存储了可变的数据,这些数据可以在widget的生命周期内改变。
实现收藏夹
在_AuthorCardState中,在类声明后添加以下属性。
bool _isFavorited = false;
现在你已经创建了一个新的状态,你需要管理它。在_AuthorCardState中用以下内容替换当前的IconButton。
IconButton(
// 1
icon: Icon(_isFavorited ? Icons.favorite : Icons.favorite_border),
iconSize: 30,
// 2
color: Colors.red[400],
onPressed: () {
// 3
setState(() {
_isFavorited = !_isFavorited;
});
},
)
下面是新状态的工作原理。
- 首先,它检查用户是否已经收藏了这张食谱卡。如果是的话,它就会显示一个填充的心。如果是假的,它就显示一个轮廓清晰的心。
- 它将颜色改为红色,以使应用程序更有活力。
- 当用户按下IconButton时,它通过调用setState()来切换_isFavorited的状态。
现在运行该应用程序,当你点击心脏按钮时,可以看到它的开关,如下图所示。
检查小部件树
现在你已经把AuthorCard变成了一个有状态的widget,你的下一步是看看元素树如何管理状态的变化。
回顾一下,框架将构建widget树,并为每个widget实例创建一个元素对象。在这种情况下,该元素是一个StatefulElement,它管理着状态对象,如下图所示。
当用户点击心脏按钮时,setState()运行并将_isFavorited切换为true。在内部,状态对象将这个元素标记为脏。这触发了对build()的调用。
这就是元素对象显示其力量的地方。它删除了旧的widget,并用一个新的Icon实例取代它,其中包含了填充的心形图标。
该框架没有重新构建整个树,而是只更新需要改变的部件。它沿着树的层次结构往下走,检查有什么变化。它重用其他的东西。
现在,当你需要访问位于层次结构中其他地方的某个部件的数据时,会发生什么?你可以使用继承的小组件。
继承的小组件
继承的部件让你从树状层次结构中的父元素中访问状态信息。
想象一下,你在widget树上有一块你想访问的数据。一个解决方案是在每个嵌套的widget上作为参数向下传递数据--但这很快就会变得很烦人和麻烦。
如果有一个集中的方法来访问这些数据,那不是很好吗?
这就是继承式小组件的用武之地! 通过在你的树中添加一个继承的小组件,你可以从它的任何一个后代中引用数据。这就是所谓的提升状态。
例如,你在以下情况下使用一个继承的部件。
- 访问一个主题对象来改变用户界面的外观。
- 调用一个API服务对象来从网上获取数据。
- 订阅流,根据收到的数据更新用户界面。
继承的部件是一个高级话题。你将在第4节 "网络、持久性和状态 "中了解到更多关于它们的信息,这一节涵盖了状态管理和Provider包--一个围绕继承的部件的包装。
关键点
- Flutter并行维护三个树:Widget、Element和RenderObject树。
- 一个Flutter应用程序是高性能的,因为它保持着它的结构,并且只更新需要重绘的部件。
- Flutter检查器是一个有用的工具,用于调试、实验和检查小部件树。
- 你应该总是从创建无状态部件开始,只有当你需要管理和维护部件的状态时才使用有状态部件。
- 继承的widgets是一个很好的解决方案,可以从树的顶端访问状态。
何去何从?
如果您想了解更多关于widget如何工作的理论,请查看以下链接。
-
Flutter和widget的详细架构概述:flutter.dev/docs/resour…
-
Flutter团队创建了一个YouTube系列,解释widget在引擎盖下的工作:www.youtube.com/playlist?li…
-
Flutter团队在中国做了一个关于如何渲染widget的讲座:youtu.be/996ZgFRENMs…
在下一章中,你将学习回到更实际的关注点上,看看如何创建可滚动的部件。