likes
comments
collection
share

Flutter笔记:build方法、构建上下文BuildContext解析

作者站长头像
站长
· 阅读数 27

Flutter笔记 build 方法解析


作者李俊才 (jcLee95)blog.csdn.net/qq_28550263 邮箱 : 291148484@163.com 本文地址blog.csdn.net/qq_28550263…

本文主要介绍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方法来构建用户界面的过程。这个过程通常发生在以下情况下:

  1. 初始化页面:当首次创建页面或小部件时,Flutter会自动调用build方法来构建初始的用户界面。这是在应用程序启动时或页面首次加载时发生的。
  2. 调用setState方法:setState是一个常用的方法,用于通知Flutter框架某些状态已更改。调用setState时,Flutter框架会重新执行与build方法相关的逻辑,以便更新界面以反映新的状态。这通常发生在响应用户交互或接收到新数据时。
  3. 父级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之间的关系的信息,以及访问应用程序主题、媒体查询等的能力。

  1. 位置信息BuildContext对象包含了有关Widget在Widget树中的位置的信息。它指示了Widget在Widget树的层次结构中的位置,包括其祖先和子孙。这对于在构建过程中查找和访问其他Widget非常有用。

  2. 主题信息BuildContext允许你访问应用程序的主题数据。通过Theme.of(context),可以获取当前上下文中的主题,从而根据主题数据自定义 Widget 的外观。

    final ThemeData theme = Theme.of(context);
    
  3. 媒体查询信息BuildContext还允许执行媒体查询,以获取有关设备屏幕的信息,如屏幕宽度、高度和方向。这对于创建响应式布局非常有用。

    final MediaQueryData mediaQuery = MediaQuery.of(context);
    final double screenWidth = mediaQuery.size.width;
    final double screenHeight = mediaQuery.size.height;
    final Orientation orientation = mediaQuery.orientation;
    
  4. 查找父级Widget:可以使用BuildContext对象来查找父级Widget,以便与其通信或访问其属性。例如,使用ModalRoute.of(context)可以获取与当前页面路由相关的信息。

    final ModalRoute<dynamic> route = ModalRoute.of(context);
    if (route != null) {
      // 可以访问路由相关信息
    }
    
  5. 错误处理BuildContext还用于错误处理。如果在build方法中发生错误,Flutter可以使用BuildContext来构建错误信息,以便开发人员能够更容易地追踪错误。

BuildContext是一个在Flutter中非常有用的对象,它使k开发者能够在build方法中访问与上下文相关的信息,以便更好地构建和定制Widget。通过适当地使用BuildContext,可以创建具有更高可复用性和响应性的Widget。

2.2 BuildContext 接口都提供了什么

名称类别描述类型
widgetgetter返回与此BuildContext相关联的Element的当前配置的Widget。Widget
ownergetter返回与此上下文相关的BuildOwner,负责管理渲染流程。BuildOwner?
mounted属性返回一个布尔值,指示与此上下文相关的widget是否当前挂载在widget树中。bool
debugDoingBuildgetter返回一个布尔值,指示与此上下文相关的widget是否正在构建中。bool
findRenderObject()方法返回与构建上下文相关的widget的RenderObject。通常在绘制回调或交互事件处理程序中使用。RenderObject?
sizegetter返回与findRenderObject返回的RenderBox的大小。通常在绘制回调或交互事件处理程序中使用。Size?
dependOnInheritedElement (InheritedElement ancestor, { Object? aspect })方法注册此构建上下文与指定的祖先InheritedElement的关联,以便在祖先InheritedWidget的值更改时重新构建。InheritedWidget
dependOnInheritedWidgetOfExactType ({Object? aspect})方法返回指定类型的最近的祖先InheritedWidget的实例,并注册此构建上下文以依赖于它T?
getInheritedWidgetOfExactType ()方法返回指定类型的最近的祖先InheritedWidget的实例,但不会注册此构建上下文以依赖于它。T?
getElementForInheritedWidgetOfExactType ()方法返回指定类型的最近的祖先InheritedWidget的InheritedElement实例。不会注册此构建上下文以依赖于它。InheritedElement?
findAncestorWidgetOfExactType ()方法返回最近的祖先widget,其类型与指定类型匹配。用于查找特定类型的祖先widget。T?
findAncestorStateOfType ()方法返回最近的祖先StatefulWidget的State对象,其类型与指定类型匹配。通常用于与祖先交互,例如滚动列表中将widget滚动到可视区域。T?
findRootAncestorStateOfType ()方法返回类型匹配的最远祖先StatefulWidget的State对象。会遍历整个widget树,直到找到匹配的祖先。T?
findAncestorRenderObjectOfType ()方法返回最近的祖先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, {DiagnosticsTreeStyle style = 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用于:

  1. 构建用户界面:build方法的主要目的是构建用户界面。通过返回一个Widget树,描述了用户界面的结构和组件的布局。Flutter框架会使用这个返回的Widget树来构建实际的UI元素。
  2. 反映UI的状态和数据:build方法的返回值通常会反映应用程序的当前状态和数据。当状态或数据发生变化时,Flutter框架会重新调用build方法,并根据新的状态构建更新后的UI。
  3. 响应用户交互:UI元素通常会包含用户可以与之交互的部分,例如按钮、输入字段等。build方法返回的Widget包括了这些交互元素的定义和行为,以便用户可以与应用程序进行互动。
  4. 组合和嵌套:Flutter的UI是通过组合和嵌套不同类型的Widget来构建的。build方法的返回值可以包含其他Widget,这样可以构建出复杂的UI结构。通过嵌套不同的Widget,可以轻松创建多层次的UI布局。
  5. 高性能和重建: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等适当的工具。