最近作了个小功能,要作一个全局的弹窗,随处均可以弹出,这个咋作呢? 说下从头至尾的思路:学习
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进来就好。
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浮层了。
洋洋洒洒写完了,上面是我作这个需求的整个分析及解决思路,你们能够参考下 ^_^。
最后,咱们在作全局弹框时,有这两种方式可选,具体看须要哪一种合适选哪一个吧~~