最近做了個小功能,要做一個全域性的彈窗,隨處都可以彈出,這個咋做呢? 說下從頭到尾的思路:
- 之前看過文章寫過如何不使用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浮層了。
洋洋灑灑寫完了,上面是我做這個需求的整個分析及解決思路,大家可以參考下 ^_^。
最後,我們在做全域性彈框時,有這兩種方式可選,具體看需要哪種合適選哪個吧~~