最近做了个小功能,要做一个全局的弹窗,随处都可以弹出,这个咋做呢? 说下从头到尾的思路:
- 之前看过文章写过如何不使用context进行路由跳转,正常情况我们都是这么写:
Navigator.of(context).pushNamed('new_page');
都是需要传一个context才可以的。 但有时我们可能需要在没法传context的时候跳转咋写呢?
我们可以这样做:
// 先新建一个navigatorKey
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
//然后,找到我们的MaterialApp
MaterialApp(
navigatorKey: navigatorKey,//加上此配置
title: 'Flutter Demo',
theme: ThemeData.light(),
home: HomePage(),
)
然后我们页面跳转就可以这样写了:
navigatorKey.currentState.pushNamed('new_page');
好了,我们实现无context跳转了。
回归正题,既然有了这个state,我们能否用里面的context呢? 然后我就兴奋的去尝试一下:
showDialog(
context: navigatorKey.currentState.context,
builder: (context) => AlertDialog(
content: Text('content'),
),
)
结果,很是失望!竟然报错了:
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.
原来这个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进来就好。
- 接下来再介绍第二种方式,浮层:
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浮层了。
洋洋洒洒写完了,上面是我做这个需求的整个分析及解决思路,大家可以参考下 ^_^。
最后,我们在做全局弹框时,有这两种方式可选,具体看需要哪种合适选哪个吧~~
本人一直在做Flutter相关开发,为了更好地互相交流相关问题,刚刚搞了个群,欢迎大家加入呀,一起学习交流Flutter知识。