flutter自定義彈窗(一):overlay

lzyprime 博客 (github)
創(chuàng)建時間:2020.08.20
qq及郵箱:2383518170

λ:

當(dāng)前flutter版本:1.20.2

1.png

需求如圖(畫的示意圖)筹裕,點擊按鈕彈出菜單區(qū)域疼进,點擊另外按鈕時胶征,關(guān)閉當(dāng)前菜單同時打開對應(yīng)菜單告唆。

按說下拉菜單應(yīng)該用 DropDownButton 或者 PopupMenuButton之類的, 但是有幾個條件滿足不了:

  1. 菜單寬度占滿屏幕。
  2. 其他按鈕可以響應(yīng)點擊缨该。

尺寸岸裙,這兩個按鈕實現(xiàn)太難或者不可来累,底層做了尺寸限制。

點擊效果懂扼,這兩個按鈕彈出效果用的PopupRoute, 就像平時跳新頁面時Navigator.of(context).push(MaterialPageRoute(...)) 一個道理禁荸,差別是PopupRoute是透過的右蒲,下層的Widget仍可見。這一效果是因為成員變量opaquefalse赶熟。
所以當(dāng)點擊 “第二個按鈕所在位置” 時瑰妄,并不會響應(yīng),頂多關(guān)閉當(dāng)前菜單映砖,因為按鈕屬于下層Widget间坐,點擊時其實是點擊的“菜單所在頁面”的空白區(qū)域。

Dialog 彈窗也是用這個實現(xiàn)的邑退。底層通過Navigator.of(...).push<T>(_DialogRoute<T>(...)) 顯示Dialog, _DialogRoute 繼承自PopupRoute

Route 是另外的知識點竹宋,另作總結(jié)。

overlay

看了看Tooltips的實現(xiàn)地技,底層用的Overlay蜈七。因為Tooltips只能顯示字符串,如果能自定義內(nèi)容就好了莫矗。

Overlay本身繼承自StatefulWidget飒硅,所以是可以直接拿來創(chuàng)建widget。但是我們上層一般都是用的 WidgetApp 或者 MaterialApp 組件作谚,這兩個組件里的 Navigator 會創(chuàng)建個 Overlay 來管理路由三娩。也就是說樹的上層節(jié)點已經(jīng)有了,所以我們可以通過 Overlay.of 拿到上層的OverlayState:

class Overlay extends StatefulWidget{
    ...
    static OverlayState of(
        BuildContext context, {
        bool rootOverlay = false,
        Widget debugRequiredFor,
      })
    ...
}
// rootOverlay: 
// 值為false, 就近查找食磕,找到樹中最近的節(jié)點; 
// 如果為true, 則去找最頂層的節(jié)點尽棕。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
    /// 存儲所有的OverlayEntry
    final List<OverlayEntry> _entries = <OverlayEntry>[];

    /// 計算OverlayEntry的插入位置
    int _insertionIndex(OverlayEntry below, OverlayEntry above) {
        if (below != null) return _entries.indexOf(below);
        if (above != null) return _entries.indexOf(above) + 1;
        return _entries.length;
    }

    /// 添加一個OverlayEntry, 在`_insertionIndex(below, above)`
    /// OverlayEntry里可以放個[Positioned]來確定位置彬伦。
    void insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above })

    /// 同insert滔悉,添加多個
    void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above })

    /// 更新當(dāng)前的Overlayentry。將newEntries更新舊有的部分
    /// 將舊有未更新的部分单绑,添加到`_insertionIndex(below, above)`
    void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry below, OverlayEntry above }){
        final old = LinkedHashSet<OverlayEntry>.from(_entries);
    
        setState(() {
          _entries..clear()..addAll(newEntriesList);
          old.removeAll(newEntriesList);
          _entries.insertAll(_insertionIndex(below, above), old);
        });
    }

}

再往下就是渲染層render的實現(xiàn)回官,_Theatre 維護個特殊的Stack

class _Theatre extends MultiChildRenderObjectWidget

自定義彈出框 OverlayEntry

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

由于Overlay底層是個特殊Stack, 所以OverlayEntry里可以放Positioned, 可以Positioned.fill看一下可控范圍:

2.png

滿屏。用Positioned控制布局位置和大小搂橙,這就是另外的事情了歉提。

關(guān)閉OverlayEntry需要調(diào)用自身的remove() 方法。注意頁面切換区转,頁面關(guān)閉等等情況苔巨,都要自己處理。

final overlayEntry = OverlayEntry(...);

overlayEntry.remove();

demo

git clone -b flutter_overlay https://github.com/lzyprime/flutter_demos.git

demo1: 菜單彈窗

3.gif

關(guān)于怎么確定按鈕下方的坐標(biāo)废离,老生常談侄泽。

demo2: 跟隨按鈕滾動

4.gif

一個部件跟隨另一個部件滾動,要用到 CompositedTransformTargetCompositedTransformFollower蜻韭,通過LayerLink綁定在一起悼尾。

const CompositedTransformTarget({
    Key key,
    @required this.link,
    Widget child,
  })

const CompositedTransformFollower({
    Key key,
    @required this.link,
    this.showWhenUnlinked = true,
    this.offset = Offset.zero,
    Widget child,
  })

  // link: LayerLink, Target 和 Follower 設(shè)置同一個LayerLink實現(xiàn)跟隨
  // showWhenUnlinked: 當(dāng)Target不存在時柿扣,是否還顯示
  // offset: 相對于Target左上角坐標(biāo)進行偏移
// example
CompositedTransformTarget(
                link: _layerLink,
                child: RaisedButton(
                  color: Colors.amberAccent,
                  key: _buttonKey,
                    onPressed: () {
                  if (_overlayEntry != null) {
                    _overlayEntry.remove();
                    _overlayEntry = null;
                    return;
                  }
                  final buttonSize = (_buttonKey.currentContext.findRenderObject() as RenderBox).size;

                  Overlay.of(context).insert(_overlayEntry = OverlayEntry(
                      builder: (context) => Positioned(child: CompositedTransformFollower(
                        link: _layerLink,
                        showWhenUnlinked: false,
                        offset: Offset(0, buttonSize.height),
                        child: Container(color: Colors.blue),
                      ),width: buttonSize.width,
                        height: 300,
                      )));
                }),
              ),

這只是demo, 細節(jié)忽略

~λ:

Overlay自由靈活,但是需要自己維護窗口關(guān)閉和顯示的時機闺魏,處理不當(dāng)未状,容易出bug。所以看需求而定析桥,非要用的話司草,,謹(jǐn)慎烹骨。

另外翻伺,這篇文章從7.26就開始寫,寫了一半沮焕。如今8.20了才完工吨岭。看源碼學(xué)東西其實很快峦树,但是做這種總結(jié)辣辫,要花好多功夫,可能比學(xué)習(xí)的過程還要麻煩魁巩。

我還有很多東西沒總結(jié)急灭,但是顧慮花功夫在總結(jié)上有多大的用處。這讓我想起了我之前搞得linux shell谷遂, 之前做后端和運維的時候葬馋,shell腳本非常熟練,從dev到外網(wǎng)全套工具鏈和自動化基本都是我寫的肾扰,可現(xiàn)在不怎么碰之后畴嘶,寫之前我得先想想,不能順手就來集晚,了窗悯。

總結(jié),我只能盡力而為了偷拔,有空就搞吧蒋院。只有足夠的積累,才會有值得總結(jié)的東西莲绰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欺旧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蛤签,更是在濱河造成了極大的恐慌切端,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顷啼,死亡現(xiàn)場離奇詭異踏枣,居然都是意外死亡,警方通過查閱死者的電腦和手機钙蒙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門茵瀑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躬厌,你說我怎么就攤上這事马昨。” “怎么了扛施?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵鸿捧,是天一觀的道長。 經(jīng)常有香客問我疙渣,道長匙奴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任妄荔,我火速辦了婚禮泼菌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啦租。我一直安慰自己哗伯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布篷角。 她就那樣靜靜地躺著焊刹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恳蹲。 梳的紋絲不亂的頭發(fā)上虐块,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音阱缓,去河邊找鬼非凌。 笑死,一個胖子當(dāng)著我的面吹牛荆针,可吹牛的內(nèi)容都是我干的敞嗡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼航背,長吁一口氣:“原來是場噩夢啊……” “哼喉悴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玖媚,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤箕肃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后今魔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勺像,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡障贸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吟宦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篮洁。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖殃姓,靈堂內(nèi)的尸體忽然破棺而出袁波,到底是詐尸還是另有隱情,我是刑警寧澤蜗侈,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布篷牌,位于F島的核電站,受9級特大地震影響踏幻,放射性物質(zhì)發(fā)生泄漏枷颊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一叫倍、第九天 我趴在偏房一處隱蔽的房頂上張望偷卧。 院中可真熱鬧,春花似錦吆倦、人聲如沸听诸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晌梨。三九已至,卻和暖如春须妻,著一層夾襖步出監(jiān)牢的瞬間仔蝌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工荒吏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敛惊,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓绰更,卻偏偏與公主長得像瞧挤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子儡湾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348