Flutter源码分析笔记:Widget类源码分析
Visit me at: https://jclee95.blog.csdn.net
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/132259681
【介绍】:本文记录阅读与分析Flutter源码 - Widget类源码分析。
Widget类是Flutter框架中的核心类之一,用于描述用户界面的一部分。它是一个不可变的描述,可以被实例化为元素(Element),后者负责管理底层的渲染树。
Widget是Flutter框架的基础,用于描述用户界面的一部分,不可变且无状态。它有一些用于实例化元素、诊断调试和比较的方法和属性,同时它的子类可以是StatelessWidget或StatefulWidget,用于构建静态或有状态的用户界面部分。
这部分源码见附录F-1。
key属性控制一个widget如何替换树中的另一个widget。如果两个widget的runtimeType和key属性分别通过operator==比较是相等的,那么新的widget将通过更新底层元素来替换旧的widget。否则,旧的元素会从树中移除,新的widget会被实例化为元素,然后插入树中。
该方法将配置实例化为一个具体的元素(Element)。一个widget可以在树中被包含零次或多次,每次被包含在树中时,都会被实例化为一个元素。
该方法用于判断一个新的widget是否可以用来更新一个已有元素的配置。根据runtimeType和key属性的比较来判断两个widget是否可以更新。
debugFillProperties(DiagnosticPropertiesBuilder properties):向诊断信息属性构建器添加属性,用于调试和诊断。在子类中可以重写以提供更多的调试信息。
返回一个数值,表示特定Widget子类型的具体编码。用于在热重载时判断已挂载元素的配置是否被修改。
实现运算符”==“用于判断两个widget是否相等,以及计算它们的哈希值。
返回此widget的简短文本描述,通常是它的runtimeType和key的组合。
Widget 是一个抽象类,继承自 DiagnosticableTree(这个类表示诊断树,主要用于提供调试信息),因此继承了一些用于调试和诊断的方法和属性。
Widget 本身没有可变状态,它的所有字段都必须是 final
。如果需要关联可变状态,应该使用 StatefulWidget,后者在被实例化为元素并添加到树中时会创建一个State对象。Widget的子类可以是 StatelessWidget(始终以相同的方式构建)或 StatefulWidget(可以在其生命周期内多次构建)。
StatefulWidget: 这是一个带有可变状态的 Widget 类别。它由两部分组成:一个是不可变的描述部分(Widget),另一个是可变的状态部分(State)。StatefulWidget 实例在构建过程中可以改变其状态,当状态发生变化时,相关的 State 对象会被重新构建以更新界面。适用于有变化状态的部分,比如用户输入、数据加载等。
StatelessWidget: 这是一个不可变的 Widget 类别,其描述和外观在整个生命周期内保持不变。StatelessWidget 实例在构建时不会持有可变状态,因此适用于不需要变化的 UI 部分,如图标、文本等。
Widget可以被多次包含在树中,每次都会被实例化为元素。如果某个 widget 在树中出现多次,它将被多次实例化。
RenderObjectWidget是Flutter渲染引擎的一部分,表示屏幕上的可见对象,它又有LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget这三个子类。
- LeafRenderObjectWidget: 这是一种将 RenderObject 无需管理子元素的 Widget 类别。它通常用于将自定义的绘制逻辑封装为 Widget,然后通过构建 RenderObject 进行绘制。
- SingleChildRenderObjectWidget: 这是一种管理单个子元素的 RenderObjectWidget 类别。它会创建一个单一的子元素,并将其作为子节点传递给 RenderObject 进行渲染。
- MultiChildRenderObjectWidget: 这是一种管理多个子元素的 RenderObjectWidget 类别。它会创建多个子元素,并将它们作为子节点传递给 RenderObject 进行渲染。比如 Stack、Column 和 Row 等都是 MultiChildRenderObjectWidget 的子类。
ProxyWidget提供一种方式来包装 Widgets,以实现特定的功能。它是一个具有子Widget的Widget,而非新的Widget
- ParentDataWidget: 这是一个用于修改子 Widget 布局约束的 ProxyWidget 子类。它在渲染树中修改子元素的布局信息,例如 Positioned 和 Align 等都是 ParentDataWidget 的子类,用于指定子元素的位置和对齐方式。
- InheritedWidget: 这是一种特殊类型的 ProxyWidget,它允许在 Widget 树中向下传递共享的数据,而不需要显式地传递。当 InheritedWidget 更新时,其子孙节点会自动重新构建。适用于需要在多个部分之间共享数据的情况,如主题、语言等。
在Flutter中,Widget 树是指由各种不同类型的Widget构成的层次结构。每个Widget描述了用户界面的一部分,可以是一个简单的元素,也可以是一个复杂的组合。这些Widget通过嵌套关系形成了一个树状结构,被称为Widget树。这种嵌套关系定义了界面中各个部分的排列和组织方式。
Widget树是构建用户界面的基本模型。当 Flutter 应用程序运行时,它会从一个 根Widget 开始,然后逐级构建出整个界面。每个Widget都有一个与之相关联的Element,负责管理底层的渲染树。渲染树最终会被转化为可视的UI元素,显示在屏幕上。
嵌套关系: Widget 树的节点由各种不同类型的 Widget 组成,这些 Widget 可以嵌套在彼此内部,形成层次结构。
不可变性: Widget 本身是 不可变 的,一旦创建就不能再进行修改。如果需要更新界面,通常是通过创建新的Widget来替换旧的Widget。
构建方式: 构建 Widget 树通常是通过构建方法来完成的。在构建方法中,你可以创建和组合不同的 Widget,从而构建出整个界面。
热重载: Flutter支持热重载,这意味着你可以在不重新启动应用程序的情况下快速修改和查看界面的变化。在热重载期间,Flutter会比较新旧Widget树的差异,并尽可能地保留应用程序的状态。
响应式: Flutter的界面是响应式的,意味着当数据发生变化时,相关的Widget会自动更新。这是通过在StatefulWidget 中管理可变状态来实现的。
/// 描述 [Element] 的配置。
///
/// 在 Flutter 框架中,组件是层次结构的中心类。一个组件是用户界面的不可变描述的一部分。
/// 组件可以被充实成元素,这些元素管理着底层的渲染树。
///
/// 组件本身没有可变状态(它们的所有字段必须是 final 的)。
/// 如果你想将可变状态与组件关联起来,可以考虑使用 [StatefulWidget],每当它被充实成元素并
/// 并且被纳入树中时,就会创建一个 [State] 对象(通过 [StatefulWidget.createState])。
///
/// 一个给定的组件可以零次或多次地包含在树中。特别地,一个给定的组件可以多次放置在树中。
/// 每次将一个组件放置在树中时,它都会被充实成一个 [Element],这意味着一个多次包含在树中的
/// 组件将会被充实多次。
///
/// [key] 属性控制了一个组件如何替换树中的另一个组件。如果两个组件的 [runtimeType] 和 [key]
/// 属性分别为 [operator==],那么新组件将通过更新底层元素(即通过调用 [Element.update]
/// 用新组件)来替换旧组件。否则,旧元素将从树中移除,新组件将被充实成一个元素,并将新元素插入树中。
///
/// 另外,将 [GlobalKey] 用作组件的 [key],可以使元素在树中移动(更改父级)而不丢失状态。
/// 当找到一个新组件(其键和类型与前一帧中同一位置的先前组件不匹配),
/// 但是在树的其他位置(前一帧中)有一个具有相同全局键的组件时,那么该组件的元素将移动到新位置。
///
/// 通常情况下,作为另一个组件唯一子组件的组件不需要明确的键。
///
/// 另请参阅:
///
/// * [StatefulWidget] 和 [State],用于在其生命周期内可以多次构建的组件。
/// * [InheritedWidget],用于引入可以被后代组件读取的环境状态。
/// * [StatelessWidget],对于在给定特定配置和环境状态的情况下始终以相同方式构建的组件。
@immutable
abstract class Widget extends DiagnosticableTree {
/// 为子类初始化 [key]。
const Widget({this.key});
/// 控制一个组件如何替换树中的另一个组件。
///
/// 如果两个组件的 [runtimeType] 和 [key] 属性分别为 [operator==],那么新组件将通过
/// 更新底层元素(即通过调用 [Element.update] 用新组件)来替换旧组件。
/// 否则,旧元素将从树中移除,新组件将被充实成一个元素,并将新元素插入树中。
///
/// 另外,将 [GlobalKey] 用作组件的 [key],可以使元素在树中移动(更改父级)而不丢失状态。
/// 当找到一个新组件(其键和类型与前一帧中同一位置的先前组件不匹配),
/// 但是在树的其他位置(前一帧中)有一个具有相同全局键的组件时,那么该组件的元素将移动到新位置。
///
/// 通常情况下,作为另一个组件唯一子组件的组件不需要明确的键。
///
/// 另请参阅:
///
/// * [Key] 和 [GlobalKey] 的讨论。
final Key? key;
/// 将此配置充实为具体实例。
///
/// 一个给定的组件可以零次或多次地包含在树中。特别地,一个给定的组件可以多次放置在树中。
/// 每次将一个组件放置在树中时,它都会被充实成一个 [Element],这意味着一个多次包含在树中的
/// 组件将会被充实多次。
@protected
@factory
Element createElement();
/// 此组件的简短文本描述。
@override
String toStringShort() {
final String type = objectRuntimeType(this, 'Widget');
return key == null ? type : '$type-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
@override
@nonVirtual
bool operator ==(Object other) => super == other;
@override
@nonVirtual
int get hashCode => super.hashCode;
/// 是否可以使用 `newWidget` 更新当前将 `oldWidget` 作为其配置的 [Element]。
///
/// 使用给定组件作为其配置的元素可以更新为使用另一个组件作为其配置,前提是两个组件具有
/// [runtimeType] 和 [key] 属性,这些属性是 [operator==]。
///
/// 如果组件没有键(它们的键为 null),则认为它们是匹配的,即使它们的子组件完全不同。
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
}
// 返回特定 `Widget` 具体子类型的数值编码。
// 这在 `Element.updateChild` 中用于确定热重载是否修改了已装载元素配置的超类。
// 每个 `Widget` 的编码必须与 `Element._debugConcreteSubtype` 中相应的 `Element` 编码匹配。
static int _debugConcreteSubtype(Widget widget) {
return widget is StatefulWidget
? 1
: widget is StatelessWidget
? 2
: 0;
}
}
/// 一个不需要可变状态的小部件。
///
/// 无状态小部件是一个小部件,通过构建一组其他更具体描述用户界面的小部件来描述用户界面的一部分。
/// 构建过程递归地继续,直到用户界面的描述完全具体(例如,完全由描述具体的 [RenderObject] 的 [RenderObjectWidget] 构成)。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=wE7khGHVkYY}
///
/// 无状态小部件在你所描述的用户界面部分不依赖于除了对象本身的配置信息和填充小部件时的 [BuildContext] 之外的任何内容时非常有用。
/// 对于可以动态改变的组合,例如由于具有内部时钟驱动的状态,或者依赖于一些系统状态,请考虑使用 [StatefulWidget]。
///
/// ## 性能注意事项
///
/// 无状态小部件的 [build] 方法通常只在三种情况下被调用:第一次将小部件插入树中时,
/// 当小部件的父级更改其配置时(参见 [Element.rebuild])以及它所依赖的 [InheritedWidget] 发生更改时。
///
/// 如果小部件的父级会定期更改小部件的配置,或者它依赖于经常更改的继承小部件,
/// 那么优化 [build] 方法的性能以保持流畅的渲染性能就显得很重要。
///
/// 有几种技术可以用来最小化重建无状态小部件的影响:
///
/// * 最小化构建方法和它创建的任何小部件所创建的传递节点的数量。例如,不要使用复杂的 [Row]、
/// [Column]、[Padding] 和 [SizedBox] 的排列来以特别花哨的方式定位单个子部件,
/// 而是考虑只使用 [Align] 或 [CustomSingleChildLayout]。不要使用多个 [Container] 并带有 [Decoration] 的
/// 错综复杂的图层来绘制恰好正确的图形效果,而是考虑使用单个 [CustomPaint] 小部件。
///
/// * 在可能的情况下使用 `const` 小部件,并为小部件提供 `const` 构造函数,
/// 以便小部件的用户也可以这样做。
///
/// * 考虑将无状态小部件重构为有状态小部件,以便它可以使用 [StatefulWidget] 中描述的一些技术,
/// 例如缓存子树的常见部分和在更改树结构时使用 [GlobalKey]。
///
/// * 如果小部件由于使用 [InheritedWidget] 而可能经常被重建,
/// 考虑将无状态小部件重构为多个小部件,其中发生更改的树的部分被推送到叶子上。
/// 例如,不要使用四个小部件构建一个树,最内部的小部件依赖于 [Theme],
/// 而是考虑将构建最内部小部件的构建函数部分分离出来,将其构建为自己的小部件,
/// 这样只有最内部的小部件在主题更改时需要重建。
/// {@template flutter.flutter.widgets.framework.prefer_const_over_helper}
/// * 在尝试创建可重用的 UI 片段时,优先使用小部件而不是助手方法。
/// 例如,如果使用函数来构建小部件,那么 [State.setState] 调用将需要 Flutter 完全重新构建返回的包装小部件。
/// 如果使用了 [Widget],Flutter 将能够高效地重新渲染只有那些真正需要更新的部分。
/// 更好的是,如果创建的小部件是 `const`,Flutter 将会短路大部分的重建工作。
/// {@endtemplate}
///
/// 这个视频更详细地解释了 `const` 构造函数的重要性以及为什么使用小部件要比使用助手方法更好。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IOyq-eTRhvo}
///
/// {@tool snippet}
///
/// 以下是一个名为 `GreenFrog` 的无状态小部件子类的骨架。
///
/// 通常,小部件具有更多的构造函数参数,每个参数都对应一个 `final` 属性。
///
/// ```dart
/// class GreenFrog extends StatelessWidget {
/// const GreenFrog({ super.key });
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(color: const Color(0xFF2DBD3A));
/// }
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet}
///
/// 下一个示例显示了更通用的小部件 `Frog`,它可以提供颜色和子部件:
///
/// ```dart
/// class Frog extends StatelessWidget {
/// const Frog({
/// super.key,
/// this.color = const Color(0xFF2DBD3A),
/// this.child,
/// });
///
/// final Color color;
/// final Widget? child;
///
/// @override
/// Widget build(BuildContext context) {
/// return ColoredBox(color: color, child: child);
/// }
/// }
/// ```
/// {@end-tool}
///
/// 按照惯例,小部件构造函数只使用命名参数。同样按照惯例,第一个参数是 [key],最后一个参数是 `child`、
/// `children` 或等效物。
///
/// 另请参阅:
///
/// * [StatefulWidget] 和 [State],用于在其生命周期内可以多次构建的小部件。
/// * [InheritedWidget],用于引入可以被后代小部件读取的环境状态。
abstract class StatelessWidget extends Widget {
/// 为子类初始化 [key]。
const StatelessWidget({super.key});
/// 创建一个 [StatelessElement] 来管理此组件在树中的位置。
///
/// 子类很少覆盖这个方法。
@override
StatelessElement createElement() => StatelessElement(this);
/// 描述此组件表示的用户界面的部分。
///
/// 当此组件在给定的 [BuildContext] 中插入树中,并且此组件引用的依赖项发生更改(例如,此组件引用的 [InheritedWidget] 发生更改)时,
/// 框架会调用此方法。这个方法可能在每一帧中被调用,除了构建一个组件之外,不应该有任何副作用。
///
/// 框架通过此方法返回的组件来替换此组件下方的子树,要么通过更新现有的子树,要么通过删除子树并充实新的子树,
/// 具体取决于此方法返回的组件是否可以更新现有子树的根,这由调用 [Widget.canUpdate] 来确定。
///
/// 通常情况下,实现会返回一个新创建的一组配置为来自此组件的构造函数和给定的 [BuildContext] 信息的小部件。
///
/// 给定的 [BuildContext] 包含有关构建此组件的位置在树中的信息。
/// 例如,上下文为该位置提供了继承的小部件集合。如果小部件在树中移动,或者如果小部件同时在多个位置插入树中,
/// 则该小部件可能会随着时间的推移使用多个不同的 [BuildContext] 参数构建。
///
/// 此方法的实现只能依赖于:
///
/// * 小部件的字段,它们本身不能随时间变化,
/// 和
/// * 使用 [BuildContext.dependOnInheritedWidgetOfExactType] 从 `context` 获得的任何环境状态。
///
/// 如果一个小部件的 [build] 方法需要依赖其他内容,应使用 [StatefulWidget]。
///
/// 另请参阅:
///
/// * [StatelessWidget],其中包含性能考虑的讨论。
@protected
Widget build(BuildContext context);
}
/// 一个具有可变状态的小部件。
///
/// 状态是(1)在小部件构建时可以同步读取的信息,以及(2)在小部件的生命周期内可能会发生变化的信息。
/// 小部件实现者有责任确保当状态发生变化时,[State] 及时被通知,使用 [State.setState]。
///
/// 有状态小部件是通过构建一组其他更具体描述用户界面的小部件来描述用户界面的一部分的小部件。
/// 构建过程递归地继续,直到用户界面的描述完全具体(例如,完全由描述具体的 [RenderObject] 的 [RenderObjectWidget] 构成)。
///
/// 当你描述的用户界面部分可以动态地改变时,有状态小部件非常有用,例如由于具有内部时钟驱动的状态,或者依赖于一些系统状态。
/// 对于只依赖于对象本身的配置信息和填充小部件时的 [BuildContext] 的组合,请考虑使用 [StatelessWidget]。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=AqCMFXEmf3w}
///
/// [StatefulWidget] 实例本身是不可变的,并且将可变状态存储在由 [createState] 方法创建的单独的 [State] 对象中,
/// 或存储在 [State] 订阅的对象中,例如 [Stream] 或 [ChangeNotifier] 对象,这些引用存储在 [StatefulWidget] 本身的 final 字段中。
///
/// 框架在每次填充 [StatefulWidget] 时都会调用 [createState],这意味着如果小部件在树中的多个位置插入,
/// 则多个 [State] 对象可能与同一个 [StatefulWidget] 关联。同样,如果从树中移除 [StatefulWidget],
/// 然后再次插入树中,框架将再次调用 [createState] 来创建一个新的 [State] 对象,简化了 [State] 对象的生命周期。
///
/// 当从树中的一个位置移动到另一个位置时,如果创建者为其 [key] 使用了 [GlobalKey],则 [StatefulWidget] 保留相同的 [State] 对象。
/// 因为具有 [GlobalKey] 的小部件最多可以在树中的一个位置使用,所以使用 [GlobalKey] 的小部件最多有一个关联的元素。
/// 当具有全局键的小部件从树的一个位置移动到另一个位置时,框架通过将与该小部件关联的(唯一的)子树从旧位置移植到新位置来利用此属性
/// (而不是在新位置重新创建子树)。与 [StatefulWidget] 相关联的 [State] 对象与其余子树一起被移植,
/// 这意味着 [State] 对象在新位置中被重用(而不是重新创建)。但是,为了有资格进行移植,小部件必须在从旧位置删除时的相同动画帧中
/// 插入新位置。
///
/// ## 性能注意事项
///
/// [StatefulWidget] 主要分为两类。
///
/// 第一类是在 [State.initState] 中分配资源并在 [State.dispose] 中释放这些资源的小部件,
/// 但不依赖于 [InheritedWidget] 或调用 [State.setState]。这些小部件通常用于应用程序或页面的根部,
/// 并通过 [ChangeNotifier]、[Stream] 或其他类似的对象与子小部件通信。遵循这种模式的有状态小部件相对便宜
/// (从 CPU 和 GPU 循环的角度来看),因为它们只会构建一次,然后永不更新。因此,它们的构建方法可以相对复杂和深入。
///
/// 第二类是使用 [State.setState] 或依赖于 [InheritedWidget] 的小部件。在应用程序的生命周期内,
/// 这些小部件通常会重建多次,因此最大限度地减少重建此类小部件的影响非常重要。
/// (它们可能还会使用 [State.initState] 或 [State.didChangeDependencies] 并分配资源,但重要的是它们会重建。)
///
/// 有几种技术可以用来最小化重建有状态小部件的影响:
///
/// * 将状态推送到叶子节点。例如,如果页面有一个滴答滴答的时钟,
/// 而不是将状态放在页面顶部并在每次时钟滴答时重新构建整个页面,
/// 创建一个专用的时钟小部件只更新自己。
///
/// * 最小化构建方法和它创建的任何小部件所创建的传递节点的数量。
/// 理想情况下,有状态小部件只会创建一个小部件,该小部件将是 [RenderObjectWidget]。
/// (显然,这并不总是切实可行的,但小部件越接近此理想,它的效率就越高。)
///
/// * 如果子树不会改变,请缓存表示该子树的小部件,并在每次可以使用时重复使用它。
/// 为此,将小部件分配给一个 `final` 状态变量,并在构建方法中重复使用它。
/// 对于小部件来说,重新使用要比创建新的(但配置相同)小部件要高效得多。
/// 另一种缓存策略是将小部件的可变部分提取到接受子部件参数的 [StatefulWidget] 中。
///
/// * 在可能的情况下使用 `const` 小部件。 (这等效于缓存小部件并重复使用它。)
///
/// * 避免更改任何创建的子树的深度,或更改子树中任何小部件的类型。
/// 例如,与其返回仅是子部件或在 [IgnorePointer] 中包装的子部件,
/// 最好始终在 [IgnorePointer] 中包装子部件,并控制 [IgnorePointer.ignoring] 属性。
/// 这是因为更改子树的深度需要重新构建、布局和绘制整个子树,
/// 而更改属性只需要对渲染树进行最小的可能更改(例如,在 [IgnorePointer] 的情况下,根本不需要布局或重绘)。
///
/// * 如果由于某种原因必须更改深度,请考虑将子树的常见部分包装在具有在状态小部件的生命周期内保持一致的 [GlobalKey] 的小部件中。
/// (如果没有其他小部件可以方便地分配键,则 [KeyedSubtree] 小部件可能对此有用。)
///
/// {@macro flutter.flutter.widgets.framework.prefer_const_over_helper}
///
/// 这个视频更详细地解释了 `const` 构造函数的重要性以及为什么使用小部件要比使用助手方法更好。
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IOyq-eTRhvo}
///
/// 有关重建小部件的机制的更多详细信息,请参阅 [Element.rebuild] 中的讨论。
///
/// {@tool snippet}
///
/// 这是一个名为 `YellowBird` 的有状态小部件子类的骨架。
///
/// 在此示例中,[State] 没有实际的状态。通常,状态表示为私有成员字段。
/// 同样,通常小部件具有更多的构造函数参数,每个参数对应一个 `final` 属性。
///
/// ```dart
/// class YellowBird extends StatefulWidget {
/// const YellowBird({ super.key });
///
/// @override
/// State<YellowBird> createState() => _YellowBirdState();
/// }
///
/// class _YellowBirdState extends State<YellowBird> {
/// @override
/// Widget build(BuildContext context) {
/// return Container(color: const Color(0xFFFFE306));
/// }
/// }
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// 该示例显示了更通用的小部件 `Bird`,它可以提供颜色和子部件,并且具有一种用于变异的内部状态和方法:
///
/// ```dart
/// class Bird extends StatefulWidget {
/// const Bird({
/// super.key,
/// this.color = const Color(0xFFFFE306),
/// this.child,
/// });
///
/// final Color color;
/// final Widget? child;
///
/// @override
/// State<Bird> createState() => _BirdState();
/// }
///
/// class _BirdState extends State<Bird> {
/// double _size = 1.0;
///
/// void grow() {
/// setState(() { _size += 0.1; });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// color: widget.color,
/// transform: Matrix4.diagonal3Values(_size, _size, 1.0),
/// child: widget.child,
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// 按照惯例,小部件构造函数只使用命名参数。同样按照惯例,第一个参数是 [key],最后一个参数是 `child`、
/// `children` 或者等价的内容。
///
/// 另请参见:
///
/// * [State],其中托管 [StatefulWidget] 的逻辑。
/// * [StatelessWidget],对于在给定特定配置和环境状态下始终以相同方式构建的小部件。
/// * [InheritedWidget],对于引入可以由后代小部件读取的环境状态的小部件。
abstract class StatefulWidget extends Widget {
/// 为子类初始化 [key]。
const StatefulWidget({super.key});
/// 创建一个 [StatefulElement] 来管理此小部件在树中的位置。
///
/// 不常见的情况下,子类会覆盖此方法。
@override
StatefulElement createElement() => StatefulElement(this);
/// 在树的给定位置为此小部件创建可变状态。
///
/// 子类应该覆盖此方法,以返回其关联的 [State] 子类的新创建实例:
///
/// ```dart
/// @override
/// State<SomeWidget> createState() => _SomeWidgetState();
/// ```
///
/// 框架可能会在 [StatefulWidget] 的生命周期内多次调用此方法。
/// 例如,如果在树中的多个位置插入小部件,则框架会为每个位置创建一个单独的 [State] 对象。
/// 同样,如果从树中删除 [StatefulWidget],然后再次插入树中,
/// 框架将再次调用 [createState] 来创建一个新的 [State] 对象,简化了 [State] 对象的生命周期。
@protected
@factory
State createState();
}
/// [StatefulWidget] 的逻辑和内部状态。
///
/// 状态是可以在小部件构建时同步读取的信息,并且可能在小部件的生命周期内发生变化。
/// 小部件实现者有责任确保当这种状态变化时,[State] 能够及时地得到通知,使用 [State.setState]。
///
/// [State] 对象通过在膨胀 [StatefulWidget] 以将其插入树中时调用 [StatefulWidget.createState] 方法
/// 来由框架创建。因为给定的 [StatefulWidget] 实例可以多次膨胀(例如,小部件同时在多个位置合并到树中),
/// 可能会与给定的 [StatefulWidget] 实例关联多个 [State] 对象。
/// 同样,如果从树中移除 [StatefulWidget],然后再次插入树中,框架将再次调用 [StatefulWidget.createState]
/// 来创建一个新的 [State] 对象,简化了 [State] 对象的生命周期。
///
/// [State] 对象具有以下生命周期:
///
/// * 框架通过调用 [StatefulWidget.createState] 来创建一个 [State] 对象。
/// * 新创建的 [State] 对象与 [BuildContext] 关联。
/// 此关联是永久的:[State] 对象永远不会更改其 [BuildContext]。
/// 但是,[BuildContext] 本身可以随着其子树一起移动到树中的其他位置。
/// * 在此时,[State] 对象的关联 [BuildContext] 可以通过 [context] 属性访问。
/// * 在调用 [dispose] 后,框架会断开 [State] 对象与 [BuildContext] 的连接。
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
/// 当前的配置。
///
/// [State] 对象的配置是相应的 [StatefulWidget] 实例。
/// 在调用 [initState] 之前,框架会使用此属性初始化。
/// 如果父级将此位置在树中更新为具有与当前配置相同的 [runtimeType] 和 [Widget.key] 的新小部件,
/// 框架将更新此属性以引用新小部件,然后调用 [didUpdateWidget],将旧配置作为参数传递。
T get widget => _widget!;
T? _widget;
/// 此状态对象的当前生命周期阶段。
///
/// 当启用断言时,框架会使用此字段来验证 [State] 对象是否按顺序移动其生命周期。
_StateLifecycle _debugLifecycleState = _StateLifecycle.created;
/// 验证创建的 [State] 是否是期望为特定 [Widget] 创建的 [State]。
bool _debugTypesAreRight(Widget widget) => widget is T;
/// 此小部件构建的树中的位置。
///
/// 在使用 [StatefulWidget.createState] 创建 [State] 对象后,在调用 [initState] 之前,
/// 框架会将 [State] 对象与 [BuildContext] 关联起来。
/// 此关联是永久的:[State] 对象永远不会更改其 [BuildContext]。
/// 但是,[BuildContext] 本身可以随着其子树一起移动到树中的其他位置。
///
/// 在调用 [dispose] 后,框架会断开 [State] 对象与 [BuildContext] 的连接。
BuildContext get context {
assert(() {
if (_element == null) {
throw FlutterError(
'This widget has been unmounted, so the State no longer has a context (and should be considered defunct). \n'
'Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.',
);
}
return true;
}());
return _element!;
}
StatefulElement? _element;
/// 当前的 [State] 对象是否在树中。
///
/// 在创建 [State] 对象后,在调用 [initState] 之前,框架通过将其与 [BuildContext] 关联起来来“安装” [State] 对象。
/// [State] 对象会一直安装到框架调用 [dispose],此后框架将不会再要求 [State] 对象重新 [build]。
///
/// 调用 [setState] 除非 [mounted] 为 true,否则会引发错误。
bool get mounted => _element != null;
/// 插入树中时调用此方法。
///
/// 框架将为其创建的每个 [State] 对象调用此方法一次。
///
/// 覆盖此方法以执行依赖于插入树中的位置(即 [context])或用于配置此对象的小部件(即 [widget])的初始化。
///
/// {@template flutter.widgets.State.initState}
/// 如果 [State] 的 [build] 方法依赖于可能自身更改状态的对象,例如 [ChangeNotifier] 或 [Stream],
/// 或其他可以订阅以接收通知的对象,请确保在 [initState]、[didUpdateWidget] 和 [dispose] 中适当地订阅和取消订阅:
///
/// * 在 [initState] 中订阅对象。
/// * 在 [didUpdateWidget] 中取消订阅旧对象,并在更新后的小部件配置需要替换对象时订阅新对象。
/// * 在 [dispose] 中取消订阅对象。
///
/// {@endtemplate}
///
/// 你不能从此方法中使用 [BuildContext.dependOnInheritedWidgetOfExactType]。
/// 但是,[didChangeDependencies] 将在此方法之后立即调用,
/// 可以在其中使用 [BuildContext.dependOnInheritedWidgetOfExactType]。
///
/// 此方法的实现应始于调用继承的方法,如 `super.initState()`。
@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterWidgetsLibrary,
className: '$State',
object: this,
);
}
}
/// 每当小部件配置更改时调用。
///
/// 如果父小部件重新构建并要求此树中的位置更新以显示具有相同 [runtimeType] 和 [Widget.key] 的新小部件,
/// 则框架将更新此 [State] 对象的 [widget] 属性以引用新小部件,然后调用此方法,并将上一个小部件作为参数传递。
///
/// 覆盖此方法以响应 [widget] 更改(例如,启动隐式动画)。
///
/// 在调用 [didUpdateWidget] 之后,框架总是会调用 [build],这意味着在 [didUpdateWidget] 中调用 [setState] 是多余的。
///
/// {@macro flutter.widgets.State.initState}
///
/// 此方法的实现应始于调用继承的方法,如 `super.didUpdateWidget(oldWidget)`。
///
/// _有关调用此方法的更多信息,请参见 [Element.rebuild] 中的讨论。_
@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) {}
/// {@macro flutter.widgets.Element.reassemble}
///
/// 除了调用此方法之外,还保证在信号重组时将调用 [build] 方法。因此,大多数小部件在 [reassemble] 方法中不需要做任何事情。
///
/// 另请参见:
///
/// * [Element.reassemble]
/// * [BindingBase.reassembleApplication]
/// * [Image],它使用此方法重新加载图像。
@protected
@mustCallSuper
void reassemble() {}
/// 通知框架此对象的内部状态已更改。
///
/// 每当更改 [State] 对象的内部状态时,请将更改放入传递给 [setState] 的函数中:
///
/// ```dart
/// setState(() { _myState = newValue; });
/// ```
///
/// 提供的回调会立即同步调用。它不能返回未来(回调不能是 `async`),因为这将不清楚状态实际上是何时被设置的。
///
/// 调用 [setState] 会通知框架此对象的内部状态已更改,以一种可能影响此子树中的用户界面的方式,
/// 这会导致框架为此 [State] 对象安排 [build]。
///
/// 如果直接更改状态而不调用 [setState],则框架可能不会安排 [build],
/// 并且此子树的用户界面可能不会更新以反映新状态。
///
/// 通常建议 [setState] 方法仅用于包装对状态的实际更改,而不是与更改相关的任何计算。
/// 例如,在此处,由 [build] 函数使用的值会递增,然后将更改写入磁盘,
/// 但只有递增操作包含在 [setState] 中:
///
/// ```dart
/// Future<void> _incrementCounter() async {
/// setState(() {
/// _counter++;
/// });
/// Directory directory = await getApplicationDocumentsDirectory(); // from path_provider package
/// final String dirName = directory.path;
/// await File('$dirName/counter.txt').writeAsString('$_counter');
/// }
/// ```
///
/// 在框架调用 [dispose] 后调用此方法会引发错误。
/// 你可以通过检查 [mounted] 属性是否为 true 来确定是否可以调用此方法。
@protected
void setState(VoidCallback fn) {
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object for a widget that '
'no longer appears in the widget tree (e.g., whose parent widget no longer '
'includes the widget in its build). This error can occur when code calls '
'setState() from a timer or an animation callback.',
),
ErrorHint(
'The preferred solution is '
'to cancel the timer or stop listening to the animation in the dispose() '
'callback. Another solution is to check the "mounted" property of this '
'object before calling setState() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if setState() is being called '
'because another object is retaining a reference to this State object '
'after it has been removed from the tree. To avoid memory leaks, '
'consider breaking the reference to this object during dispose().',
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
"hasn't been inserted into the widget tree yet. It is not necessary to call "
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.',
),
]);
}
return true;
}());
final Object? result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".',
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().',
),
]);
}
// 我们忽略了其他类型的返回值,以便你可以执行以下操作:
// setState(() => x = 3);
return true;
}());
_element!.markNeedsBuild();
}
/// 当此对象从树中移除时调用。
///
/// 框架在从树中移除此 [State] 对象时调用此方法。
/// 在某些情况下,框架将重新将 [State] 对象插入到树的另一部分中
/// (例如,如果包含此 [State] 对象的子树由于使用了 [GlobalKey] 而从树的一个位置移植到另一个位置)。
/// 如果发生这种情况,框架将调用 [activate],以便 [State] 对象有机会重新获取在 [deactivate] 中释放的任何资源。
/// 然后还将调用 [build],以便 [State] 对象有机会适应其在树中的新位置。
/// 如果框架确实重新插入此子树,则将在将子树从树中移除的动画帧结束之前执行此操作。
/// 因此,[State] 对象可以推迟释放大多数资源,直到框架调用它们的 [dispose] 方法。
///
/// 子类应该覆盖此方法,以清除此对象与树中其他元素之间的任何链接(例如,如果你向祖先提供了指向后代的 [RenderObject] 的指针)。
///
/// 此方法的实现应该以调用继承的方法结束,如 `super.deactivate()`。
///
/// 另请参见:
///
/// * [dispose],如果小部件永久从树中移除,则在 [deactivate] 之后调用。
@protected
@mustCallSuper
void deactivate() {}
/// 当此对象通过 [deactivate] 被移除后,重新插入树中时调用。
///
/// 在大多数情况下,[State] 对象在被停用后 _不会_ 被重新插入树中,
/// 并且将调用其 [dispose] 方法以表示其已准备好进行垃圾回收。
///
/// 但在某些情况下,[State] 对象在被停用后,框架将其重新插入树的另一部分中
/// (例如,如果包含此 [State] 对象的子树由于使用了 [GlobalKey] 而从树的一个位置移植到另一个位置)。
/// 如果发生这种情况,框架将调用 [activate],以便 [State] 对象有机会重新获取在 [deactivate] 中释放的任何资源。
/// 然后还将调用 [build],以便对象有机会适应其在树中的新位置。
/// 如果框架确实重新插入此子树,则将在将子树从树中移除的动画帧结束之前执行此操作。
/// 因此,[State] 对象可以推迟释放大多数资源,直到框架调用它们的 [dispose] 方法。
///
/// 框架不会在第一次将 [State] 对象插入树中时调用此方法。在这种情况下,框架将调用 [initState]。
///
/// 此方法的实现应该以调用继承的方法开始,如 `super.activate()`。
@protected
@mustCallSuper
void activate() {}
/// 当此对象永久从树中移除时调用。
///
/// 框架在永远不会再次构建此 [State] 对象时调用此方法。在框架调用 [dispose] 后,
/// [State] 对象被视为未安装,[mounted] 属性为 false。此时调用 [setState] 是错误的。
/// 生命周期的这个阶段是终端的:已被处置的 [State] 对象无法再次安装。
///
/// 子类应该覆盖此方法以释放此对象保留的任何资源(例如,停止任何活动的动画)。
///
/// {@macro flutter.widgets.State.initState}
///
/// 此方法的实现应该以调用继承的方法结束,如 `super.dispose()`。
@protected
@mustCallSuper
void dispose() {
assert(_debugLifecycleState == _StateLifecycle.ready);
assert(() {
_debugLifecycleState = _StateLifecycle.defunct;
return true;
}());
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
}
/// 描述由此小部件表示的用户界面的部分。
///
/// 框架在许多不同的情况下调用此方法。例如:
///
/// * 调用 [initState] 后。
/// * 调用 [didUpdateWidget] 后。
/// * 收到 [setState] 的调用后。
/// * 此 [State] 对象的依赖项发生更改后(例如,由先前的 [build] 引用的 [InheritedWidget] 更改)。
/// * 在调用 [deactivate],然后将 [State] 对象重新插入树的另一个位置。
///
/// 该方法可能在每一帧中被调用,除了构建小部件外,不应具有任何副作用。
///
/// 框架使用此方法返回的小部件替换此小部件下方的子树,可以通过更新现有子树或删除子树并填充新的子树来实现,具体取决于此方法返回的小部件是否可以更新现有子树的根部,由调用 [Widget.canUpdate] 确定。
///
/// 通常,实现会返回一个新创建的小部件集合,这些小部件通过此小部件的构造函数、给定的 [BuildContext] 和此 [State] 对象的内部状态进行配置。
///
/// 给定的 [BuildContext] 包含有关正在构建此小部件的树中的位置的信息。例如,上下文为该树中的此位置提供了继承小部件集。[BuildContext] 参数始终与此 [State] 对象的 [context] 属性相同,并将在此对象的生命周期内保持相同。在此处提供 [BuildContext] 参数是为了使此方法与 [WidgetBuilder] 的签名匹配。
///
/// ## 设计讨论
///
/// ### 为什么在 [State] 上而不是在 [StatefulWidget] 上放置 [build] 方法?
///
/// 在 [StatefulWidget] 上放置一个 `Widget build(BuildContext context)` 方法,
/// 而不是在 [StatefulWidget] 上放置一个 `Widget build(BuildContext context, State state)` 方法,
/// 可以为开发人员在子类化 [StatefulWidget] 时提供更多的灵活性。
///
/// 例如,[AnimatedWidget] 是 [StatefulWidget] 的子类,为其子类引入了一个抽象的 `Widget build(BuildContext context)` 方法供其子类实现。
/// 如果 [StatefulWidget] 已经有一个接受 [State] 参数的 [build] 方法,那么 [AnimatedWidget] 将被强制向其子类提供其 [State] 对象,
/// 即使其 [State] 对象是 [AnimatedWidget] 的内部实现细节。
///
/// 从概念上讲,[StatelessWidget] 也可以以类似的方式实现为 [StatefulWidget] 的子类。
/// 如果 [build] 方法在 [StatefulWidget] 而不是在 [State] 上,那就不可能了。
///
/// 将 [build] 函数放在 [State] 上而不是 [StatefulWidget] 上还有助于避免与隐式捕获 `this` 相关的一类与闭包相关的错误。
/// 如果在 [StatefulWidget] 的 [build] 函数中定义闭包,那么该闭包将隐式捕获 `this`,即当前小部件实例,
/// 并且在范围内具有该实例的(不可变的)字段:
///
/// ```dart
/// //(这不是有效的 Flutter 代码)
/// class MyButton extends StatefulWidgetX {
/// MyButton({super.key, required this.color});
///
/// final Color color;
///
/// @override
/// Widget build(BuildContext context, State state) {
/// return SpecialWidget(
/// handler: () { print('color: $color'); },
/// );
/// }
/// }
/// ```
///
/// 例如,假设父级以蓝色构建 `MyButton`,则打印函数中的 `$color` 引用蓝色,正如预期的那样。
/// 现在,假设父级以绿色重建 `MyButton`。第一次构建创建的闭包仍然隐式引用原始小部件,
/// 并且 `$color` 仍然打印蓝色,即使小部件已更新为绿色;如果该闭包的寿命超过其小部件,它将打印过时的信息。
///
/// 相比之下,在 [State] 对象上的 [build] 函数中创建的闭包隐式捕获 [State] 实例而不是小部件实例:
///
/// ```dart
/// class MyButton extends StatefulWidget {
/// const MyButton({super.key, this.color = Colors.teal});
///
/// final Color color;
/// // ...
/// }
///
/// class MyButtonState extends State<MyButton> {
/// // ...
/// @override
/// Widget build(BuildContext context) {
/// return SpecialWidget(
/// handler: () { print('color: ${widget.color}'); },
/// );
/// }
/// }
/// ```
///
/// 现在,当父级以绿色重建 `MyButton` 时,第一次构建创建的闭包仍然引用 [State] 对象,其在重建过程中保留,
/// 但框架已将该 [State] 对象的 [widget] 属性更新为引用新的 `MyButton` 实例,`${widget.color}` 打印绿色,正如预期的那样。
///
/// 另请参见:
///
/// * [StatefulWidget],其中包含有关性能考虑的讨论。
@protected
Widget build(BuildContext context);
/// 当此 [State] 对象的依赖项发生更改时调用。
///
/// 例如,如果上一次调用 [build] 引用了稍后更改的 [InheritedWidget],框架将调用此方法通知此对象有关更改的信息。
///
/// 此方法在 [initState] 之后立即调用。可以从此方法中安全地调用 [BuildContext.dependOnInheritedWidgetOfExactType]。
///
/// 由于框架始终在依赖项更改后调用 [build],所以子类很少覆盖此方法。有些子类确实会覆盖此方法,因为当其依赖项发生更改时,它们需要进行一些昂贵的工作(例如网络获取),
/// 而且对于每次构建来说,这些工作的成本太高了。
@protected
@mustCallSuper
void didChangeDependencies() {}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
assert(() {
properties.add(EnumProperty<_StateLifecycle>(
'lifecycle state', _debugLifecycleState,
defaultValue: _StateLifecycle.ready));
return true;
}());
properties
.add(ObjectFlagProperty<T>('_widget', _widget, ifNull: 'no widget'));
properties.add(ObjectFlagProperty<StatefulElement>('_element', _element,
ifNull: 'not mounted'));
}
}
- 点赞
- 收藏
- 关注作者
评论(0)