美文网首页
flutter:全局 context 在 navigator 与

flutter:全局 context 在 navigator 与

作者: 李小轰 | 来源:发表于2022-04-24 14:36 被阅读0次

    引言:什么是 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 进行嵌套。

    相关文章

      网友评论

          本文标题:flutter:全局 context 在 navigator 与

          本文链接:https://www.haomeiwen.com/subject/bektyrtx.html