Flutter笔记build 方法解析
作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
本文地址:https://blog.csdn.net/qq_28550263/article/details/133556333
本文主要介绍Flutter中的build方法和构建上下文对象相关知识。
目 录
1. 什么是 build 方法
在Flutter中,build
方法是一个重要的生命周期方法,它用于构建和返回一个Widget树,这个Widget树将用于渲染用户界面。每当需要重新构建界面时,Flutter就会调用build
方法。
以下是build
方法的基本结构和用法:
Widget build(BuildContext context) { // 在这里构建和返回Widget树 }
如果将Flutter 的组件分成有状态组件(Stateful Widgets)和无状态组件(Stateless Widgets)。这两种类型的组件在构建方法build
的位置上略有区别。下面分别简单回顾一下。
1.1 有状态组件(Stateful Widgets)的 build 方法
- 对于有状态组件,
build
方法通常位于与State
对象相关联的build
方法内部。每个有状态组件都有一个关联的State
对象,build
方法是在State
对象内部定义的。
class MyStatefulWidget extends StatefulWidget { @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState(); } class _MyStatefulWidgetState extends State<MyStatefulWidget> { @override Widget build(BuildContext context) { // 在这里构建和返回Widget树 } }
在有状态组件中,build
方法通常用于根据组件的状态来构建UI。当组件的状态发生变化时,Flutter会自动调用build
方法来更新UI。
1.2 无状态组件(Stateless Widgets)的 build 方法
- 对于无状态组件,
build
方法通常位于组件类的直接内部。无状态组件不包含可变状态,因此build
方法可以直接在组件类内部定义。
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { // 在这里构建和返回Widget树 } }
无状态组件的build
方法通常用于构建基于输入属性(Widget
的构造函数参数)的静态UI。由于无状态组件不包含状态,因此它们的build
方法不会在状态变化时被调用。
1.3 Flutter构建页面的过程
Flutter构建过程是指Flutter框架如何根据需要自动调用build
方法来构建用户界面的过程。这个过程通常发生在以下情况下:
- 初始化页面:当首次创建页面或小部件时,Flutter会自动调用
build
方法来构建初始的用户界面。这是在应用程序启动时或页面首次加载时发生的。 - 调用
setState
方法:setState
是一个常用的方法,用于通知Flutter框架某些状态已更改。调用setState
时,Flutter框架会重新执行与build
方法相关的逻辑,以便更新界面以反映新的状态。这通常发生在响应用户交互或接收到新数据时。 - 父级Widget需要重建:如果一个父级Widget发生了重建,它的子级Widget的
build
方法也会被调用。这是因为父级Widget的重建可能导致子级Widget的属性或上下文发生变化,因此子级Widget的build
方法需要更新以反映这些变化。
这个构建过程是Flutter的核心机制之一,它允许应用程序动态地响应用户操作和状态变化,并且能够高效地更新用户界面。Flutter框架会负责管理build
方法的调用,并在需要时进行优化,以确保界面保持同步和高性能。
Flutter的构建过程通过自动调用build
方法来创建和更新用户界面,确保应用程序能够在不同情况下呈现正确的界面状态。这种自动化的方式使开发人员能够专注于界面的描述和逻辑,而无需手动管理UI的刷新。
2. 构建上下文对象(BuildContext)
2.1 回顾:contex 参数都有哪些用
build
方法接受一个BuildContext
对象作为参数。BuildContext
是一个用于获取与构建上下文相关信息的对象,例如主题、媒体查询信息等。它是构建过程中的上下文环境。
在Flutter中,BuildContext
(上下文对象)是一个非常重要的参数,它在build
方法中作为参数传递给 Widget 构建函数。BuildContext
对象提供了有关Widget在Widget树中的位置和与父级Widget之间的关系的信息,以及访问应用程序主题、媒体查询等的能力。
- 位置信息:
BuildContext
对象包含了有关Widget在Widget树中的位置的信息。它指示了Widget在Widget树的层次结构中的位置,包括其祖先和子孙。这对于在构建过程中查找和访问其他Widget非常有用。 - 主题信息:
BuildContext
允许你访问应用程序的主题数据。通过Theme.of(context)
,可以获取当前上下文中的主题,从而根据主题数据自定义 Widget 的外观。
final ThemeData theme = Theme.of(context);
- 媒体查询信息:
BuildContext
还允许执行媒体查询,以获取有关设备屏幕的信息,如屏幕宽度、高度和方向。这对于创建响应式布局非常有用。
final MediaQueryData mediaQuery = MediaQuery.of(context); final double screenWidth = mediaQuery.size.width; final double screenHeight = mediaQuery.size.height; final Orientation orientation = mediaQuery.orientation;
- 查找父级Widget:可以使用
BuildContext
对象来查找父级Widget,以便与其通信或访问其属性。例如,使用ModalRoute.of(context)
可以获取与当前页面路由相关的信息。
final ModalRoute<dynamic> route = ModalRoute.of(context); if (route != null) { // 可以访问路由相关信息 }
- 错误处理:
BuildContext
还用于错误处理。如果在build
方法中发生错误,Flutter可以使用BuildContext
来构建错误信息,以便开发人员能够更容易地追踪错误。
BuildContext
是一个在Flutter中非常有用的对象,它使k开发者能够在build
方法中访问与上下文相关的信息,以便更好地构建和定制Widget。通过适当地使用BuildContext
,可以创建具有更高可复用性和响应性的Widget。
2.2 BuildContext 接口都提供了什么
名称 | 类别 | 描述 | 类型 |
widget | getter | 返回与此BuildContext相关联的Element的当前配置的Widget。 | Widget |
owner | getter | 返回与此上下文相关的BuildOwner,负责管理渲染流程。 | BuildOwner? |
mounted | 属性 | 返回一个布尔值,指示与此上下文相关的widget是否当前挂载在widget树中。 | bool |
debugDoingBuild | getter | 返回一个布尔值,指示与此上下文相关的widget是否正在构建中。 | bool |
findRenderObject() | 方法 | 返回与构建上下文相关的widget的RenderObject。通常在绘制回调或交互事件处理程序中使用。 | RenderObject? |
size | getter | 返回与findRenderObject返回的RenderBox的大小。通常在绘制回调或交互事件处理程序中使用。 | Size? |
dependOnInheritedElement (InheritedElement ancestor, { Object? aspect }) |
方法 | 注册此构建上下文与指定的祖先InheritedElement的关联,以便在祖先InheritedWidget的值更改时重新构建。 | InheritedWidget |
dependOnInheritedWidgetOfExactType <T extends InheritedWidget>({Object? aspect}) |
方法 | 返回指定类型的最近的祖先InheritedWidget的实例,并注册此构建上下文以依赖于它。 | T? |
getInheritedWidgetOfExactType <T extends InheritedWidget>() |
方法 | 返回指定类型的最近的祖先InheritedWidget的实例,但不会注册此构建上下文以依赖于它。 | T? |
getElementForInheritedWidgetOfExactType <T extends Widget>() |
方法 | 返回指定类型的最近的祖先InheritedWidget的InheritedElement实例。不会注册此构建上下文以依赖于它。 | InheritedElement? |
findAncestorWidgetOfExactType () |
方法 | 返回最近的祖先widget,其类型与指定类型匹配。用于查找特定类型的祖先widget。 | T? |
findAncestorStateOfType <T extends State>() |
方法 | 返回最近的祖先StatefulWidget的State对象,其类型与指定类型匹配。通常用于与祖先交互,例如滚动列表中将widget滚动到可视区域。 | T? |
findRootAncestorStateOfType <T extends State>() |
方法 | 返回类型匹配的最远祖先StatefulWidget的State对象。会遍历整个widget树,直到找到匹配的祖先。 | T? |
findAncestorRenderObjectOfType <T extends RenderObject>() |
方法 | 返回最近的祖先RenderObjectWidget的实例,其类型与指定类型匹配。通常在特殊情况下使用,以改变祖先的布局或绘制行为。 | T? |
visitAncestorElements (ConditionalElementVisitor visitor ) |
方法 | 从当前构建上下文开始,向上遍历祖先Element,为每个祖先调用提供的回调函数。遍历会在回调返回false或达到根widget时停止。 | |
visitChildElements (ElementVisitor visitor ) |
方法 | 遍历此构建上下文的子级Element,为每个子级调用提供的回调函数。通常用于在构建后立即对子级执行操作,例如在子级中查找特定类型的widget。 | |
dispatchNotification (Notification notification ) |
方法 | 启动此通知在给定构建上下文上冒泡。通知将传递给具有适当类型参数的任何祖先的NotificationListener widget。 | |
describeElement (String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) |
方法 | 返回与当前构建上下文关联的Element的描述。用于调试目的。 | DiagnosticsNode |
describeWidget (String name , {DiagnosticsTreeStylestyle = DiagnosticsTreeStyle.errorProperty}) |
方法 | 返回与当前构建上下文关联的Widget的描述。用于调试目的。 | DiagnosticsNode |
describeMissingAncestor ({ required Type expectedAncestorType }) |
方法 | 添加关于当前构建上下文缺少特定类型祖先 widget 的描述。通常用于调试。 | List |
describeOwnershipChain (String name) |
方法 | 添加关于从特定Element到错误报告的所有权链的描述。用于调试目的。 | DiagnosticsNode |
2.3 一个例子:原生组件Theme的原理分析
我们之前提到,通过Theme.of(context),可以获取当前上下文中的主题,从而根据主题数据自定义 Widget 的外观:
final ThemeData theme = Theme.of(context);
Theme是Flutter框架预先为开发者封装好的一个组件。请看Flutter源码中Theme类的of静态方法源代码:
static ThemeData of(BuildContext context) { // 获取与当前BuildContext关联的_InheritedTheme实例 final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); // 获取与当前BuildContext关联的MaterialLocalizations实例 final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations); // 获取本地化脚本的类别(如中文、英文等),如果未找到则默认为英语类别 final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike; // 获取InheritedTheme中的主题数据,如果未找到则使用默认的_fallbackTheme final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme; // 返回根据主题数据和脚本类别本地化的ThemeData实例 return ThemeData.localize(theme, theme.typography.geometryThemeFor(category)); }
这段代码的作用是从当前的 BuildContext 中获取与主题相关的信息,包括主题数据和本地化信息,并返回一个本地化的 ThemeData 实例。这个实例基于当前的主题和脚本类别(用于本地化),以确保应用的界面元素与用户的地区和语言习惯相匹配。其中:
final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
获取与当前BuildContext关联的_InheritedTheme实例。获取与当前 BuildContext 关联的 _InheritedTheme 实例的目的是访问应用程序的主题信息。在许多 Flutter 应用程序中,主题包括颜色、字体、形状和其他视觉属性,这些属性会影响整个应用程序的外观和感觉。
其中,_InheritedTheme 实例通常不是开发者手动创建的,而是由 Flutter 框架自动创建和管理的,它在顶层组件(如MaterialApp)中自动创建。
在Flutter应用程序中,通常会有一个顶层的 MaterialApp 或 CupertinoApp,这些是应用程序的入口点,并且它们会创建一个根部的 BuildContext。这个根部的 BuildContext 在整个应用程序中都可以访问,因此 _InheritedTheme 实例也会放置在这个根部的 BuildContext 下,以确保主题信息在整个应用程序中都是可用的。
2.4 BuildContext 的本质
在Flutter源码中,BuildContext 是一个接口,它与一个 Element(Flutter 元素)相关联。这种关联如此之深以至于Flutter的注释写道:
/// [BuildContext] objects are actually [Element] objects. The [BuildContext] /// interface is used to discourage direct manipulation of [Element] objects.
即:BuildContext 对象实际上是 Element 对象。BuildContext接口用于阻止对[Element]对象的直接操作。
事实上需要指出的是,BuildContext 并不继承自 Element 类,但 BuildContext 实际上代表了一个 Element 对象的上下文或环境。BuildContext 类是 Element 类的一个辅助接口,用于在构建 widget 树时提供有关 Element 的信息和操作。
BuildContext 对象是 Element 对象的一种引用或描述,它提供了一种在构建过程中与 Element 进行交互的方式,包括查找 Element 的 RenderObject、注册依赖关系、获取祖先 widget 等操作。BuildContext 的实例是通过 Element 类的方法传递给 widget 的构建方法(build)的。
3. build 方法的返回值
Flutter中的build
方法的返回值是Widget
对象,该Widget描述了用户界面的外观和布局。通过在build
方法中返回不同的Widget树,可以实现不同的界面布局和交互效果,从而创建丰富而动态的应用程序。
build
方法返回Widget
用于:
- 构建用户界面:
build
方法的主要目的是构建用户界面。通过返回一个Widget
树,描述了用户界面的结构和组件的布局。Flutter框架会使用这个返回的Widget
树来构建实际的UI元素。 - 反映UI的状态和数据:
build
方法的返回值通常会反映应用程序的当前状态和数据。当状态或数据发生变化时,Flutter框架会重新调用build
方法,并根据新的状态构建更新后的UI。 - 响应用户交互:UI元素通常会包含用户可以与之交互的部分,例如按钮、输入字段等。
build
方法返回的Widget
包括了这些交互元素的定义和行为,以便用户可以与应用程序进行互动。 - 组合和嵌套:Flutter的UI是通过组合和嵌套不同类型的
Widget
来构建的。build
方法的返回值可以包含其他Widget
,这样可以构建出复杂的UI结构。通过嵌套不同的Widget
,可以轻松创建多层次的UI布局。 - 高性能和重建:
build
方法返回Widget
的方式使Flutter框架能够在需要时高效地重建UI。当需要更新UI时,Flutter会比较新旧Widget
树,找出差异,然后只重建发生变化的部分,而不是整个UI。这种机制有助于提高应用程序的性能。
4. setState方法与重构
setState 方法是Flutter框架提供的一个重要方法,用于通知框架某个State对象的内部状态已经发生了变化,并且需要重新构建用户界面以反映这种变化。
setState 方法的作用是在调用时立即同步地执行传入的回调函数,并且不能返回Future。这是因为setState要确保状态的变化在界面重建之前生效。
在回调函数中更新了State对象的状态时,setState 方法会通知Flutter框架,告诉它这个State对象的内部状态已经发生了变化。
setState方法的本质在于_element!.markNeedsBuild();:
@protected void setState(VoidCallback fn) { assert(() { // ... return true; }()); _element!.markNeedsBuild(); }
也就是说它的本质是通过标记State对象关联的元素(Element)为需要重新构建,从而触发UI的刷新。
在Flutter中,State 对象与 Element 相关联,Element 负责实际构建和管理 Widget。调用 setState 方法时,它会导致相关的 State 对象被标记为 “dirty”(脏节点),表示其内部状态已更改。
_element!.markNeedsBuild(); 这行代码的作用是标记与当前 State 对象关联的 Element 为需要重新构建。这是为了通知 Flutter 框架,与该 State相关的 Element 应该在下一个UI帧中进行重建,以便反映 State 对象的新状态。
提供使用setState方法,其实就是上面说的:
/// interface is used to discourage direct manipulation of [Element] objects.
它主要是为了避免直接操作 Element 对象,但是我们之前也说过,BuildContext 对象其实就表示当前所关联的 Element 对象,因此理论上完全可以直接在 build 方法下使用 context (BuildContext)参数访问相关的Element 的方法,就包括了markNeedsBuild方法:
(context as Element).markNeedsBuild();
5. 注意
不要在build
方法中执行耗时操作。由于build
方法可能会多次被调用,因此不应该在其中执行耗时操作,例如网络请求或大量计算。如果需要在构建过程中执行此类操作,请使用异步方法或FutureBuilder
等适当的工具。