Flutter 写全局弹框的心路历程(dialog和overlay)

最近作了个小功能,要作一个全局的弹窗,随处均可以弹出,这个咋作呢? 说下从头至尾的思路:学习

  1. 以前看过文章写过如何不使用context进行路由跳转,正常状况咱们都是这么写: Navigator.of(context).pushNamed('new_page');

都是须要传一个context才能够的。 但有时咱们可能须要在无法传context的时候跳转咋写呢?ui

咱们能够这样作:spa

// 先新建一个navigatorKey
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

//而后,找到咱们的MaterialApp
MaterialApp(
    navigatorKey: navigatorKey,//加上此配置
    title: 'Flutter Demo',
    theme: ThemeData.light(),
    home: HomePage(),
)
复制代码

而后咱们页面跳转就能够这样写了:code

navigatorKey.currentState.pushNamed('new_page');router

好了,咱们实现无context跳转了。对象

回归正题,既然有了这个state,咱们可否用里面的context呢? 而后我就兴奋的去尝试一下:路由

showDialog(
    context: navigatorKey.currentState.context,
    builder: (context) => AlertDialog(
    content: Text('content'),
   ),
)
复制代码

结果,非常失望!居然报错了:rem

Log: The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.get

原来这个context只能用于路由处理,为何呢?源码

从调用栈看了下,

showDialog->showGeneralDialog->Navigator.of(context, rootNavigator: useRootNavigator).pushxxx

最后看到了这里:

static NavigatorState of(
    BuildContext context, {
    bool rootNavigator = false,
    bool nullOk = false,
  }) {
    final NavigatorState navigator = rootNavigator
        ? context.findRootAncestorStateOfType<NavigatorState>()
        : context.findAncestorStateOfType<NavigatorState>();
    assert(() {
      if (navigator == null && !nullOk) {
        throw FlutterError(
          'Navigator operation requested with a context that does not include a Navigator.\n'
          'The context used to push or pop routes from the Navigator must be that of a '
          'widget that is a descendant of a Navigator widget.'
        );
      }
      return true;
    }());
    return navigator;
  }
复制代码

咱们看到了错误那个串字符,而后咱们拿出核心的:

findRootAncestorStateOfType<NavigatorState>findAncestorStateOfType<NavigatorState>

用过Inheritedxxx的应该比较熟悉这个,是用来从叶子节点,经过context向上查找根组件对象的,这里也就是寻找NavigatorState对象。

但从对应的实现来看,这两种方式查找初始值都是Element ancestor = _parent;,也就是从parent开始找,而当前的context就是这个state,他的parent天然是再也找不到了。所以这种简单的方式是不行了。

但也不要失望,至少咱们从此次错误中,咱们还能从showGeneralDialog发现这个:

Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(xxx

哦,原来对话框也是一种路由页面,因此咱们能够仿照源码改出一份来,这里我就不说了,思路就是push一个本身写的dialogrouter,push进来就好。


  1. 接下来再介绍第二种方式,浮层:Overlay的方式: Overlay的平常使用,好比popupwindow之类的,使用方法:
final overlay = Overlay.of(context);// 获取一个overlay

// 建立一个OverlayEntry
OverlayEntry entry = OverlayEntry(
  builder: (context) {
    return Material(
      type: MaterialType.transparency,
      child: Stack(
        children: <Widget>[
          Positioned(
            top: 200,
            left: 200,
            child: GestureDetector(
              onTap: () {
                entry.remove();
                entry = null;
              },
              child: Container(
                color: Colors.redAccent
                child: Text('hahaha'),
                width: 100,
                height: 100,
              ),
            ),
          ),
        ],
      ),
    );
  },
);

// 添加进来便可显示
overlay.insert(entry);
复制代码

好了,看完这个小demo,咱们发现,弹浮窗也须要context。 咱们先试一下:final overlay = Overlay.of(navigatorKey.currentState.context);

运行后,发现overlay是空,也是不能直接用,为何呢?

咱们看一下Overlay这个Widget在哪初始化的,通过搜索,发现是在Navigator里面build初始化的,也就是说,overlay是Navigator的child。

那通过上面dialog的经验,这里同样的问题,也是找不到的,由于也是从navigator的parent开始的,确定找不到。

那Overlay该怎么用呢?这个又不是路由,不能push。

说实话,当时我没有思路,我就各类搜啊搜~~

咦,发现了个给力的库,顺便给你们推荐下:bot_toast

支持各类弹框,toast,对话框,通知,跨页面啥的都支持。

用完后,我学习了下他的实现方式,用的就是Overlay的方式,看到他的获取Overlay的方式。

核心就是:使用了NavigatorState里面的overlay对象,我很惊讶,这个navigator里面还有这么个方法?

看了下源码,果真:OverlayState get overlay => _overlayKey.currentState;

那就好说了,咱们能够用刚刚那个navigatorKey来获取overlay了,获取方式:navigatorKey.currentState.overlay, 好了,有了这个overlay,咱们就能够随时add浮层了。

洋洋洒洒写完了,上面是我作这个需求的整个分析及解决思路,你们能够参考下 ^_^。

最后,咱们在作全局弹框时,有这两种方式可选,具体看须要哪一种合适选哪一个吧~~

相关文章
相关标签/搜索