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浮層了。

洋洋灑灑寫完了,上面是我作這個需求的整個分析及解決思路,你們能夠參考下 ^_^。

最後,咱們在作全局彈框時,有這兩種方式可選,具體看須要哪一種合適選哪一個吧~~

相關文章
相關標籤/搜索