flutter:全局 context 在 navigator 与 overlay 中的运用

引言:什么是 context

flutter 中的概念 '万物皆 widget '。flutter 的UI布局由一个个 widget 叠加组合而成,而每一个 widget 都会对应 一个 Element, Element 类内部会实现 BuildContext 接口。编码中,我们使用的 context 指向 widget 树中的具体 UI 节点。

源码截图

context 在项目中的使用场景

在 flutter 编程中,我们常用 context 来实现如下功能:

  1. navigator 导航进行页面的跳转。
Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => PageA(),
      ),
    )
  1. 弹窗 Dialog
showDialog(context: context, builder: (context) {
      return Dialog(
        child: Page(),
      );
    })
  1. 获取主题配置
final themeData = Theme.of(context);
  1. 添加 Overlay 图层 (常用于自定义 loading,toast 等能力支持)
Overlay.of(context)?.insert(? extends OverlayEntry);

如何维护一个全局 context

利用 MaterialApp 节点的 navigatorKey 属性,维护一个适用于全局的 BuildContext。在需要使用 context 的地方可直接使用全局维护的 context 进行页面跳转,showLoading,toast 等功能实现。

创建一个维护 GlobalContext 的工具类 NavigatorProvider (可直接copy使用)

import 'package:flutter/material.dart';

/// 用于提供全局的 navigatorContext
class NavigatorProvider {
  final GlobalKey<NavigatorState> _navigatorKey = new GlobalKey<NavigatorState>(debugLabel: 'Rex');

  static final NavigatorProvider _instance = NavigatorProvider._();

  NavigatorProvider._();

  /// 赋值给根布局的 materialApp 上
  /// navigatorKey.currentState.pushName('url') 可直接用于跳转
  static GlobalKey<NavigatorState> get navigatorKey => _instance._navigatorKey;

  /// 可用于 跳转,overlay-insert(toast,loading) 使用
  static BuildContext? get navigatorContext => _instance._navigatorKey.currentState?.context;
}

在 MaterialApp 根节点上进行应用注册 navigatorKey

void main() {
  runApp(
    MaterialApp( //为什么这里会嵌套两层 MaterialApp,我们在下面进行解说
      home: MaterialApp(
        navigatorKey: NavigatorProvider.navigatorKey,
        routes: {
          '/pageA': (BuildContext context) => PageA(),
        },
        home: Scaffold(
          body: TestNavigatorWidget(),
        ),
      ),
    ),
  );
}

TestNavigatorWidget 是小编写的一个测试页面,页面内包含两个功能按键(1. 跳转页面。 2. toast )

class TestNavigatorWidget extends StatelessWidget {
  const TestNavigatorWidget({Key? key}) : super(key: key);

  ///跳转页面
  void _jumpPageA() {
    NavigatorProvider.navigatorKey.currentState?.pushNamed('/pageA');
  }

  ///toast
  void _overLayToast() {
    final globalContext = NavigatorProvider.navigatorContext;
    if (globalContext != null) {
      ToastUtils.toast('首页的 toast', globalContext);
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextButton(
              onPressed: _overLayToast,
              child: Text('toast'),
            ),
            TextButton(
              onPressed: _jumpPageA,
              child: Text('跳转下一个页面'),
            ),
          ],
        ),
      ),
    );
  }
}
class PageA extends StatelessWidget {
  const PageA({Key? key}) : super(key: key);

  ///toast
  void _overLayToast() {
    final globalContext = NavigatorProvider.navigatorContext;
    if (globalContext != null) {
      ToastUtils.toast('PageA的 toast', globalContext);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('PageA')),
      body: Center(
        child: TextButton(
          onPressed: _overLayToast,
          child: Text('PageA toast'),
        ),
      ),
    );
  }
}

效果图如下:


代码分析

    1. 可直接使用全局 context 进行页面跳转
      NavigatorProvider.navigatorKey.currentState?.pushNamed('/pageA')
    1. ToastUtils 是小编封装的 toast 工具类,借助三方库 fluttertoast: ^8.0.9 使用 overlay 图层添加的原理实现。工具类中使用的 context 正是在根节点中注册的NavigatorProvider 提供的全局 context。
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';

class ToastUtils {
  late FToast _fToast;
  static ToastUtils _instance = ToastUtils._();

  ToastUtils._() {
    _fToast = FToast();
  }

  static void toast(String message, BuildContext context) {
    _instance._fToast.init(context);
    _instance._fToast.showToast(
      child: _ToastEntry(message),
      gravity: ToastGravity.BOTTOM,
      toastDuration: Duration(seconds: 2),
    );
  }

///toast使用的UI
class _ToastEntry extends StatelessWidget {
  final String message;

  const _ToastEntry(this.message, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(
        horizontal: 24.0,
        vertical: 12.0,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(25.0),
        color: Colors.greenAccent,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.check),
          SizedBox(width: 12.0),
          Text(message),
        ],
      ),
    );
  }
}

使用这段代码的同学别忘了在 pubspec.yaml 文件中添加三方库依赖哟 ~

fluttertoast: ^8.0.9
    1. 细心的同学已经发现了,小编在 main方法中,app 的根节点使用了两层 MaterialApp 进行嵌套,这是由于 Overlay 的添加特性造成的。

overlay 的 insert 操作最终会转换成 Stack 布局,而实际上 insert 添加的图层是在所提供context 对应节点的父级节点上进行操作。

demo对应树状结构图

因为这个特性,如果我们要使用一个全局的 context 用于操作 overlay ,那么就要求这个全局的 context 需要拥有一个父节点。

如果仅仅是使用全局context进行导航操作(跳转、dialog),则无需使用两层 MaterialApp 进行嵌套。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容