天天看點

【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#

      随着業務的擴充和延伸,需要的功能也是多種多樣,而同一種效果可以有多種實作方案;小菜今天學習一下通過 Overlay 實作基本的懸浮引導效果;

      Overlay 以浮層的方式管理單獨的 item 存儲在棧中(後進先出);Overlay 其源碼也是采用的 Stack 浮層,将 OverEntry 逐個加入到 Overlay 中進行展示,OverEntry 可以使用 Positioned 或 AnimatedPositioned 在 Overlay 中定義自身的位置;

      當建立 MaterialApp 時,它會自動建立一個 Navigator,之後建立一個 Overlay,然後利用這個 Navigator 來管理路由中的界面;

源碼分析

const Overlay({
    Key key,
    this.initialEntries = const <OverlayEntry>[],
})

class OverlayEntry 
  OverlayEntry({
    @required this.builder,
    bool opaque = false,
    bool maintainState = false,
  })
}
           

      分析源碼可知,Overlay 主要是由 OverlayEntry 浮層元素組成的,并以棧的方式存儲;opaque 為目前浮層元素是否遮蓋整個 Overlay 浮層;maintainState 一般與 opaque 共同使用,是否将不透明的浮層元素添加到 Widget Tree 中;

案例嘗試

      Overlay 作為浮層的應用效果很廣泛,網上很多老師都通過 Overlay 實作自定義 Toast / Dialog / PopupMenu / List item 等,但小菜嘗試通過 Overlay 實作更新過程中的新手引導;

【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#

      Overlay 主要是通過 insert / insertAll 方式加入 OverEntry 浮層元素,通過 remove 移除浮層元素;

insert One OverEnrty

      如果僅需展示一個 OverEntry 浮層元素,可以通過 insert 加入到 Overlay 中,也可以通過 insertAll 加入僅有一個 OverEntry 的數組;最終通過 remove 關閉浮層元素,注意數組中的元素要全部 remove;

// insert
overlayEntry = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[Positioned(
        top: (height - 200) * 0.5, left: (width - 200) * 0.5,
        child: GestureDetector(
            onTap: () => overlayEntry.remove(),
            child: _itemContainer(Colors.blue.withOpacity(0.6))))
  ]);
});
Overlay.of(context).insert(overlayEntry);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#
// insertAll
overlayEntry = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[Positioned(
        top: (height - 200) * 0.5, left: (width - 200) * 0.5,
        child: GestureDetector(
            onTap: () { overlayEntry.remove(); overlayEntryList.clear(); },
            child: _itemContainer(Colors.brown.withOpacity(0.6))))
  ]);
});
overlayEntryList.add(overlayEntry);
Overlay.of(context).insertAll(overlayEntryList);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#

insert Three OverEntrys

      如果需要展示多個 OverEntry 浮層元素時,隻能用 insertAll 添加到 Overlay 中,其中預設是以棧方式加入的;其中 insertAll 會一次性的把所有 OverEntry 均加入到 Overlay 中;

overlayEntryList.add(OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
        child: GestureDetector(
            onTap: () {
              --overIndex;
              overlayEntryList[overIndex].remove();
              overlayEntryList.removeLast();
            },
            child: _itemContainer(Colors.red.withOpacity(0.6))))
  ]);
}));
overlayEntryList.add(OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5, left: (width - 200) * 0.5,
        child: GestureDetector(
            onTap: () {
              --overIndex;
              overlayEntryList[overIndex].remove();
              overlayEntryList.removeLast();
            },
            child: _itemContainer(Colors.orange.withOpacity(0.6))))
  ]);
}));
overlayEntryList.add(OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
        child: GestureDetector(
            onTap: () {
              --overIndex;
              overlayEntryList[overIndex].remove();
              overlayEntryList.removeLast();
            },
            child: _itemContainer(Colors.blue.withOpacity(0.6))))
  ]);
}));
overIndex = overlayEntryList.length;
Overlay.of(context).insertAll(overlayEntryList);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#

      若需要逐次展示多個 OverlayEntry 可以在點選事件中單獨加入新的 OverlayEntry;

overlayEntry = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
        child: GestureDetector(
            onTap: () {
              overlayEntry.remove();
              Overlay.of(this.context).insert(overlayEntry2);
            },
            child: _itemContainer(Colors.red.withOpacity(0.6))))
  ]);
});
overlayEntry2 = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5, left: (width - 200) * 0.5,
        child: GestureDetector(
            onTap: () {
              overlayEntry2.remove();
              Overlay.of(this.context).insert(overlayEntry3);
            },
            child: _itemContainer(Colors.orange.withOpacity(0.6))))
  ]);
});
overlayEntry3 = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
        child: GestureDetector(onTap: () => overlayEntry3.remove(), child: _itemContainer(Colors.blue.withOpacity(0.6))))
  ]);
});
Overlay.of(context).insert(overlayEntry);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#

注意事項

1. Overlay 為全局覆寫,并非目前 Page,需要重新定義傳回按鍵等;若沒有 remove 則傳回上一個頁面依然展示浮層元素;若 remove 其他未加入浮層的元素會傳回失敗;
return WillPopScope(
    onWillPop: () async {
      if (overListIndex == 6) {
        for (int i = overlayEntryList.length; i > 0; i--) {
          overlayEntryList[i - 1].remove();
        }
        overlayEntryList.clear();
        overIndex = 0;
      } else if (overListIndex == 7) {
        overlayEntry.remove();
      } else if (overListIndex == 8) {
        overlayEntry2.remove();
      } else if (overListIndex == 9) {
        overlayEntry3.remove();
      }
      if (overIndex == 4) {
        overlayEntry.remove();
        overlayEntry0.remove();
      } else if (overIndex == 3) {
        overlayEntry2.remove();
        overlayEntry0.remove();
      } else if (overIndex == 2) {
        overlayEntry3.remove();
        overlayEntry0.remove();
      } else if (overIndex == 5) {
        overlayEntry.remove();
      }
      overIndex = 0;
      return true;
    },
    child: Container(...)
);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#
2. 使用 insertAll 添加浮層元素時,不要同時加入多次同一個 OverlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
        child: GestureDetector(
            onTap: () => overlayEntry.remove(),
            child: _itemContainer(Colors.red.withOpacity(0.6))))
  ]);
});
// 錯誤寫法,加入多次同一個 OverlayEntry
overlayEntryList.add(overlayEntry);
overlayEntryList.add(overlayEntry);
overlayEntryList.add(overlayEntry);

Overlay.of(this.context).insertAll(overlayEntryList);
           
【Flutter 專題】89 圖解基本 Overlay 懸浮新手引導 #yyds幹貨盤點#
3. opaque = true 時會完全覆寫之前的浮層元素,為不透明的,且不可透過目前浮層點選下一個浮層元素;maintainState 為在上層元素 opaque = true,即不透明的完全覆寫下層元素時,被覆寫的這個元素設定的 maintainState 是否提前建構;
overlayEntryList.add(OverlayEntry(builder: (context) {
  return Stack(children: <Widget>[
    Positioned( top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
        child: GestureDetector(
            onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
            child: _itemContainer(Colors.red.withOpacity(0.6))))
  ]);
}));
overlayEntryList.add(OverlayEntry(
    opaque: true, maintainState: true,
    builder: (context) {
      return Material(
          color: Colors.amber.withOpacity(0.4),
          child: Stack(children: <Widget>[
            Positioned( top: (height - 200) * 0.5, left: (width - 200) * 0.5,
                child: GestureDetector(
                    onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
                    child: _itemContainer(Colors.orange.withOpacity(0.6))))
          ]));
    }));
overlayEntryList.add(OverlayEntry(
    opaque: true, maintainState: false,
    builder: (context) {
      return Material(
          color: Colors.lightBlueAccent.withOpacity(0.4),
          child: Stack(children: <Widget>[
            Positioned( top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
                child: GestureDetector(
                    onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
                    child: _itemContainer(Colors.blue.withOpacity(0.6))))
          ]));
    }));
overIndex = overlayEntryList.length;
Overlay.of(context).insertAll(overlayEntryList);
           

繼續閱讀