最近作了個小功能,要作一個全局的彈窗,隨處均可以彈出,這個咋作呢? 說下從頭至尾的思路:學習
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浮層了。
洋洋灑灑寫完了,上面是我作這個需求的整個分析及解決思路,你們能夠參考下 ^_^。
最後,咱們在作全局彈框時,有這兩種方式可選,具體看須要哪一種合適選哪一個吧~~