flutter:上下文位置context

  1. BuildContext 是什么
  2. context 从哪里来
  3. context 能做什么
    1. 访问上层 widget 提供的数据
    2. 获取 MediaQuery 信息
      1. MediaQuery 是什么
      2. 查找流程
      3. 举例说明
      4. 树形图
  4. 实际用法

BuildContext 是什么

  • 类型:BuildContext 是一个 抽象类(接口)。
  • 每个 widget 在 widget 树中的位置,都会对应一个 BuildContext 对象。
  • 你可以把它看成是 Flutter 给 widget 提供的 运行环境句柄

在源码里,BuildContext 其实就是 Element 的接口:

abstract class BuildContext {
  Widget get widget;
  BuildContext? get parent;
  InheritedWidget dependOnInheritedWidgetOfExactType<T extends InheritedWidget>();
  ...
}

context 从哪里来

在 Flutter 中,每个 StatefulWidgetState、每个 StatelessWidgetbuild 方法都会接收一个 BuildContext context 参数:

@override
Widget build(BuildContext context) {
  return Text("Hello");
}

这个 context 参数就是 当前 widget 在树中的上下文位置

同一个 widget,如果被插入树的不同位置,会生成不同的 context

context 能做什么

核心能力是:通过 context 在 widget 树中“向上查找”父级信息

访问上层 widget 提供的数据

Flutter 的查找机制:

  1. 当前 widget 的 BuildContext 出发

  2. 沿着 widget 树向上(父节点方向) 逐级查找;

  3. 找到的第一个(最靠近当前 widget 的)匹配 widget(通常是某种 InheritedWidget 或者带状态的 widget);

  4. 这就是 “最近的”。

  5. 比如:

    • Theme.of(context) → 获取最近的 Theme

    • Navigator.of(context) → 获取最近的 Navigator

    • Form.of(context) → 获取最近的 FormState


MaterialApp(
  theme: ThemeData(primaryColor: Colors.blue),
  home: Theme(
    data: ThemeData(primaryColor: Colors.red), // 局部 Theme
    child: Builder(
      builder: (context) {
        final theme = Theme.of(context);
        print(theme.primaryColor); // 会输出 Red
        return Container();
      },
    ),
  ),
);
  • Flutter 会先从 Buildercontext 往上查找。

  • 最近的 Theme 是我们手动包裹的 红色 Theme,所以拿到的是红色。

  • 如果把局部 Theme 去掉,那它会继续往上找到 MaterialApp 提供的蓝色 Theme。

  • “最近” = 离当前位置 widget 最近的父 Theme


Form(// 外层
  child: Column(
    children: [
      Form( // 内层
        child: Builder(
          builder: (context) {
            final formState = Form.of(context);
            print(formState); // 获取的是里层 Form 的 FormState
            return TextFormField();
          },
        ),
      ),
    ],
  ),
);
  • 这里有两个 Form,一个外层,一个内层。

  • Builder 里调用 Form.of(context),会先找到内层的 Form → 返回内层的 FormState

  • 如果内层的 Form 不存在,那么才会继续向上找到外层的 Form。

  • “最近” = 离当前位置 层级最近的 Form

获取 MediaQuery 信息

MediaQuery 是什么

MediaQuery 是一个 InheritedWidget,它在 widget 树的顶层提供屏幕和环境信息。信息包括:

  • size(屏幕逻辑宽高)
  • padding(系统状态栏、刘海、底部导航栏的安全区域)
  • devicePixelRatio(设备像素比)
  • platformBrightness(亮/暗模式)

通常情况下,MaterialAppWidgetsApp 会在应用的最外层自动插入一个 MediaQuery


为什么MediaQuery.of(context)能代表屏幕大小?

  • Flutter 框架启动时,会从系统(Android/iOS)获取屏幕实际大小(物理像素),再换算成逻辑像素。
  • 这个信息被注入到 根部的 MediaQuery widget 中。
  • 所以,只要在 MediaQuery 的子树里MediaQuery.of(context) 就能拿到屏幕信息。

查找流程

MediaQuery.of(context) 的查找流程:

  • 当前 context 出发,向上查找最近的 MediaQuery
  • 找到后,返回其中保存的 MediaQueryData
  • MediaQueryData.size 就是当前屏幕的逻辑分辨率。

举例说明

@override
Widget build(BuildContext context) {
  final size = MediaQuery.of(context).size;
  return Scaffold(
    appBar: AppBar(title: Text("屏幕大小")),
    body: Center(
      child: Text("宽: ${size.width}, 高: ${size.height}"),
    ),
  );
}
  • 必须在子树里:如果 context 不在 MediaQuery 的子树内(比如你在 runApp 里直接用 MediaQuery.of(context)),会报错:

    No MediaQuery ancestor found in the widget tree
    
  • 局部覆盖:也可以手动包裹一个 MediaQuery,改变子树的屏幕数据(比如模拟小屏幕)。

    MediaQuery(
      data: MediaQuery.of(context).copyWith(size: Size(200, 400)),
      child: MyWidget(),
    )
    

    MyWidget 内调用 MediaQuery.of(context).size 会得到 200x400,而不是实际屏幕大小。

树形图

MaterialApp
 └── MediaQuery (根部,存储屏幕大小、padding 等)
      └── Scaffold
           ├── AppBar
           └── Column
                ├── TextFormField
                └── Builder / 其他 Widget
                     └── 调用 MediaQuery.of(context)
                          ↑
                          向上查找最近的 MediaQuery → 命中根部的 MediaQuery
  • MaterialApp 内部会插入一个 MediaQuery,作为全局根部环境提供者。
  • 当你在 任意子树 widget 里调用 MediaQuery.of(context)
    • Flutter 从 context 向上查找;
    • 一直查到这个 MediaQuery
    • 返回其中的 MediaQueryData(包含 size = 屏幕逻辑大小)。

实际用法

part of '../ducafe_ui_core.dart';

/// theme 主题颜色扩展
/// `context.colors.primary` 可以这样使用
extension ThemeColorsExtensions on BuildContext {
  // ignore: library_private_types_in_public_api
  _ThemeColors get colors => _ThemeColors(
        primary: _primaryColor,
        primaryLight: _primaryColorLight,
        primaryDark: _primaryColorDark,
        canvas: _canvasColor,
        scaffoldBackground: _scaffoldBackgroundColor,
        card: _cardColor,
        divider: _dividerColor,
        focus: _focusColor,
        hover: _hoverColor,
        highlight: _highlightColor,
        splash: _splashColor,
        unselectedWidget: _unselectedWidgetColor,
        disabled: _disabledColor,
        secondaryHeader: _secondaryHeaderColor,
        dialogBackground: _dialogBackgroundColor,
        indicator: _indicatorColor,
        hint: _hintColor,
        scheme: _colorScheme,
        shadow: _shadowColor,
      );

  // 获取当前主题
  ThemeData get _theme => Theme.of(this);

  // 获取颜色方案
  ColorScheme get _colorScheme => _theme.colorScheme;

  // 主要颜色
  Color get _primaryColor => _theme.primaryColor;
  Color get _primaryColorLight => _theme.primaryColorLight;
  Color get _primaryColorDark => _theme.primaryColorDark;

  // 次要颜色
  Color get _secondaryHeaderColor => _theme.secondaryHeaderColor;

  // 其他主题颜色
  Color get _canvasColor => _theme.canvasColor;
  Color get _scaffoldBackgroundColor => _theme.scaffoldBackgroundColor;
  Color get _cardColor => _theme.cardColor;
  Color get _dividerColor => _theme.dividerColor;
  Color get _focusColor => _theme.focusColor;
  Color get _hoverColor => _theme.hoverColor;
  Color get _highlightColor => _theme.highlightColor;
  Color get _splashColor => _theme.splashColor;
  Color get _unselectedWidgetColor => _theme.unselectedWidgetColor;
  Color get _disabledColor => _theme.disabledColor;
  Color get _dialogBackgroundColor => _theme.dialogBackgroundColor;
  Color get _indicatorColor => _theme.indicatorColor;
  Color get _hintColor => _theme.hintColor;
  Color get _shadowColor => _theme.shadowColor;
}

/// Helper class that allows to use a color like:
/// `context.colors.primary`
class _ThemeColors {
  const _ThemeColors({
    required this.primary,
    required this.primaryLight,
    required this.primaryDark,
    required this.canvas,
    required this.shadow,
    required this.scaffoldBackground,
    required this.card,
    required this.divider,
    required this.focus,
    required this.hover,
    required this.highlight,
    required this.splash,
    required this.unselectedWidget,
    required this.disabled,
    required this.secondaryHeader,
    required this.dialogBackground,
    required this.indicator,
    required this.hint,
    required this.scheme,
  });

  /// See [ThemeData.primaryColor].
  final Color primary;

  /// See [ThemeData.primaryColorLight].
  final Color primaryLight;

  /// See [ThemeData.primaryColorDark].
  final Color primaryDark;

  /// See [ThemeData.canvasColor].
  final Color canvas;

  /// See [ThemeData.shadowColor].
  final Color shadow;

  /// See [ThemeData.scaffoldBackgroundColor].
  final Color scaffoldBackground;

  /// See [ThemeData.cardColor].
  final Color card;

  /// See [ThemeData.dividerColor].
  final Color divider;

  /// See [ThemeData.focusColor].
  final Color focus;

  /// See [ThemeData.hoverColor].
  final Color hover;

  /// See [ThemeData.highlightColor].
  final Color highlight;

  /// See [ThemeData.splashColor].
  final Color splash;

  /// See [ThemeData.unselectedWidgetColor].
  final Color unselectedWidget;

  /// See [ThemeData.disabledColor].
  final Color disabled;

  /// See [ThemeData.secondaryHeaderColor].
  final Color secondaryHeader;

  /// See [ThemeData.dialogBackgroundColor].
  final Color dialogBackground;

  /// See [ThemeData.indicatorColor].
  final Color indicator;

  /// See [ThemeData.hintColor].
  final Color hint;

  /// See [ThemeData.colorScheme].
  final ColorScheme scheme;
}

在项目里,我可以直接这么调用上下文的主题颜色:

@override
Widget build(BuildContext context) {
  context.colors.scheme.primary
}

context.colors实际不是 Flutter 原生 API,而是 扩展方法(extension) 提供的。最终获得的是一个_ThemeColors对象。

extension ThemeColorsExtensions on BuildContext {
  // ignore: library_private_types_in_public_api
  _ThemeColors get colors => _ThemeColors(
        primary: _primaryColor,
    // ......
    )
}

_primaryColor这个不是属性,是在调用get方法

Color get _primaryColor => _theme.primaryColor;

_theme也是get方法的方法名,获得主题。this这里是指BuildContext

ThemeData get _theme => Theme.of(this);

×

喜欢就点赞,疼爱就打赏