Flutter 89: 圖解基本 Overlay 懸浮新手引導(dǎo)

??????隨著業(yè)務(wù)的擴展和延伸,需要的功能也是多種多樣,而同一種效果可以有多種實現(xiàn)方案;小菜今天學習一下通過 Overlay 實現(xiàn)基本的懸浮引導(dǎo)效果;

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

??????當創(chuàng)建 MaterialApp 時,它會自動創(chuàng)建一個 Navigator,之后創(chuàng)建一個 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 作為浮層的應(yīng)用效果很廣泛,網(wǎng)上很多老師都通過 Overlay 實現(xiàn)自定義 Toast / Dialog / PopupMenu / List item 等,但小菜嘗試通過 Overlay 實現(xiàn)升級過程中的新手引導(dǎo);


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

insert One OverEnrty

??????如果僅需展示一個 OverEntry 浮層元素,可以通過 insert 加入到 Overlay 中,也可以通過 insertAll 加入僅有一個 OverEntry 的數(shù)組;最終通過 remove 關(guān)閉浮層元素,注意數(shù)組中的元素要全部 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);
// 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);

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);


??????若需要逐次展示多個 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);

注意事項

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(...)
);
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);
3. opaque = true 時會完全覆蓋之前的浮層元素,為不透明的,且不可透過當前浮層點擊下一個浮層元素;maintainState 為在上層元素 opaque = true,即不透明的完全覆蓋下層元素時,被覆蓋的這個元素設(shè)置的 maintainState 是否提前構(gòu)建;
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);

??????Overlay 案例源碼


??????小菜對 Overlay 的嘗試還比較基礎(chǔ),使用場景也比較小,如有錯誤,請多多指導(dǎo)!

阿策小和尚

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容