Flutter 入門指北(Part 9)之彈窗和提示(SnackBar该溯、BottomSheet、Dialog)

該文已授權(quán)公眾號 「碼個蛋」别惦,轉(zhuǎn)載請指明出處

前面的小節(jié)把常用的一些部件都介紹了狈茉,這節(jié)介紹下 Flutter 中的一些操作提示。Flutter 中的操作提示主要有這么幾種 SnackBar掸掸、BottomSheet氯庆、Dialog,因為 Dialog 樣式比較多,放最后講好了

SnackBar

SnackBar 的源碼相對簡單

const SnackBar({
    Key key,
    @required this.content, // 提示信息
    this.backgroundColor, // 背景色
    this.action, // SnackBar 尾部的按鈕点晴,用于一些回退操作等
    this.duration = _kSnackBarDisplayDuration, // 停留的時長感凤,默認 4000ms
    this.animation, // 進出動畫
  })

例如我們需要實現(xiàn)一個功能,修改某個值粒督,修改后給用戶一個提示陪竿,同時給用戶一個撤銷該操作的按鈕,那么就可以通過 SnackBar 來簡單實現(xiàn)屠橄。還有就是 SnackBar 可以和 floatingActionButton 完美的配合族跛,彈出的時候不會遮擋住 fab

class _PromptDemoPageState extends State<PromptDemoPage> {
  var count = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  // 自增操作
  increase() {
    setState(() => count++);
  }

  // 自減操作
  decrease() {
    setState(() => count--);
  }

  _changeValue(BuildContext context) {
    increase();
    Scaffold.of(context).showSnackBar(SnackBar(
        content: Text('當(dāng)前值已修改'),
        action: SnackBarAction(label: '撤銷', onPressed: decrease),
        duration: Duration(milliseconds: 2000)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Prompt Demo'),
      ),
      body: Column(children: <Widget>[
        Text('當(dāng)前值:$count', style: TextStyle(fontSize: 20.0)),
        Expanded(
            // 為了方便拓展,我這邊提取了 `snackBar` 的方法锐墙,并把按鈕放在列表
            child: ListView(padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), children: <Widget>[
          // SnackBar 需要提供一個包含 context礁哄,但是 context 不能是 Scaffold 節(jié)點下的 context,所以需要通過 Builder 包裹一層
          Builder(builder: (context) => RaisedButton(onPressed: () => _changeValue(context), child: Text('修改當(dāng)前值'))),
        ]))
      ]),
      // 當(dāng) SnackBar 彈出時溪北,fab 會上移一段距離
      floatingActionButton: Builder(
          builder: (context) => FloatingActionButton(onPressed: () => _changeValue(context), child: Icon(Icons.send))),
    );
  }
}

可以看下最后的效果圖桐绒,請注意看 fab 和值的變化:

snackbar.gif

BottomSheet

BottomSheet 看命名就知道是從底部彈出的菜單,展示 BottomSheet 有兩種方式之拨,分別是 showBottomSheetshowModalBottomSheet茉继,兩種方式只有在展示類型上的差別,方法調(diào)用無差蚀乔,而且 showBottomSheetfab 有組合動畫烁竭,showModalBottomSheet 則沒有,看下實際的例子吧吉挣。在 ListView 中增加一個 BottomSheet 的按鈕派撕,因為 BottomSheet 需要的 context 也不能是 Scaffold 下的 context,所以需要通過 Builder 進行包裹一層睬魂,然后增加 _showBottomSheet 的方法

 _showBottomSheet(BuildContext context) {
    showBottomSheet(
      context: context,
      builder: (context) => ListView(
              // 生成一個列表選擇器
              children: List.generate(
            20,
            (index) => InkWell(
                child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
                onTap: () {
                  print('tapped item ${index + 1}');
                  Navigator.pop(context);
                }),
          )),
    );
  }

showBottomSheet 替換成 showModalBottomSheet 就是另外一種展示方式了终吼,內(nèi)部不需要做任何改變,我們看下兩種的運行效果:

bottom_sheet.gif

可以看到 showBottomSheet 會充滿整個屏幕汉买,然后 fab 會跟隨一起到 AppBar 的底部位置衔峰,而 showModalBottomSheet 展示的高度不會超過半個屏幕的高度,但是 fab 被其遮擋了蛙粘。假如我們只需要展示 2-3 個 item垫卤,但是按照剛才的方式 showModalBottomSheet 的高度太高了,那我們可以在 ListView 外層包裹一層 Container出牧,然后指定 height 即可

_showModalBottomSheet(BuildContext context) {
    showModalBottomSheet(
      context: context,
      builder: (context) => Container(
            child: ListView(
                children: List.generate(
              2,
              (index) => InkWell(
                  child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
                  onTap: () {
                    print('tapped item ${index + 1}');
                    Navigator.pop(context);
                  }),
            )),
            height: 120,
          ),
    );
  }

修改高度后的效果:

modal_bottom_sheet.gif

Dialog

相對于 SnackBarBottomSheet穴肘,Dialog 的使用場景相對會更多,在 MaterialDesign 下舔痕,Dialog 主要有 3 種:AlertDialog评抚,SimpleDialogAboutDialog豹缀,當(dāng)然在 Cupertino 風(fēng)格下也有相應(yīng)的 Dialog,因為這個系列以 MaterialDesign 風(fēng)格為主慨代,所以 Cupertiono 等下次有時間再寫吧邢笙。

AlertDialog

ListView 中增加一個 AlertDialog 的按鈕,用于點擊顯示 AlertDialog 用侍匙,然后加入顯示 AlertDilaog 的方法氮惯,并將按鈕的 onPressed 指向該方法,Dialogcontext 可以是 Scaffold 下的 context想暗,所以不需要用 Builder 來包裹一層妇汗。

_showAlertDialog() {
    showDialog(
        // 設(shè)置點擊 dialog 外部不取消 dialog,默認能夠取消
        barrierDismissible: false,
        context: context,
        builder: (context) => AlertDialog(
              title: Text('我是個標(biāo)題...嗯说莫,標(biāo)題..'),
              titleTextStyle: TextStyle(color: Colors.purple), // 標(biāo)題文字樣式
              content: Text(r'我是內(nèi)容\(^o^)/~, 我是內(nèi)容\(^o^)/~, 我是內(nèi)容\(^o^)/~'),
              contentTextStyle: TextStyle(color: Colors.green), // 內(nèi)容文字樣式
              backgroundColor: CupertinoColors.white,
              elevation: 8.0, // 投影的陰影高度
              semanticLabel: 'Label', // 這個用于無障礙下彈出 dialog 的提示
              shape: Border.all(),
              // dialog 的操作按鈕杨箭,actions 的個數(shù)盡量控制不要過多,否則會溢出 `Overflow`
              actions: <Widget>[
                // 點擊增加顯示的值
                FlatButton(onPressed: increase, child: Text('點我增加')),
                // 點擊減少顯示的值
                FlatButton(onPressed: decrease, child: Text('點我減少')),
                // 點擊關(guān)閉 dialog储狭,需要通過 Navigator 進行操作
                FlatButton(onPressed: () => Navigator.pop(context), 
                           child: Text('你點我試試.')),
              ],
            ));
  }

最后看下效果:

alert_dialog.gif
SimpleDialog

SimpleDialog 相比于 AlertDialog 少了 contentaction 參數(shù)互婿,多了 children 屬性,需要傳入 Widget 列表辽狈,那就可以自定義全部內(nèi)容了擒悬。那我們這里就實現(xiàn)一個性別選擇的 Dialog,選擇后通過 Taost 提示選擇的內(nèi)容稻艰,Taost 就是之前導(dǎo)入的第三方插件,先看下效果圖吧

simple_dialog.gif

只要實現(xiàn) children 是個列表選擇器就可以了侈净,比較簡單尊勿,直接上代碼

_showSimpleDialog() {
    showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context) => SimpleDialog(
              title: Text('我是個比較正經(jīng)的標(biāo)題...\n選擇你的性別'),
              // 這里傳入一個選擇器列表即可
              children: _genders
                  .map((gender) => InkWell(
                        child: Container(height: 40.0, child: Text(gender), alignment: Alignment.center),
                        onTap: () {
                          Navigator.pop(context);
                          Fluttertoast.showToast(msg: '你選擇的性別是 $gender');
                        },
                      ))
                  .toList(),
            ));
  }
AboutDialog

AboutDialog 主要是用于展示你的 App 或者別的相關(guān)東西的內(nèi)容信息的,平時用的比較少畜侦,顯示 AboutDialog 有兩種方式可以展示元扔,一種是前面一樣的 showDialog 方法,傳入一個 AboutDialog 實例旋膳,還有中方法是直接調(diào)用 showAboutDialog 方法澎语。我們還是一樣在列表加個按鈕,并指向顯示 AboutDialog 的事件验懊。

_showAboutDialog() {
    showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context) => AboutDialog(
              // App 的名字
              applicationName: 'Flutter 入門指北',
              // App 的版本號
              applicationVersion: '0.1.1',
              // App 基本信息下面會顯示一行小字擅羞,主要用來顯示版權(quán)信息
              applicationLegalese: 'Copyright: this is a copyright notice topically',
              // App 的圖標(biāo)
              applicationIcon: Icon(Icons.android, size: 28.0, color: CupertinoColors.activeBlue),
              // 任何你想展示的
              children: <Widget>[Text('我是個比較正經(jīng)的對話框內(nèi)容...你可以隨便把我替換成任何部件,只要你喜歡(*^▽^*)')],
            ));
  }

也可以通過 showAboutDialog 實現(xiàn)同樣的效果

  _showAboutDialog() {
    showAboutDialog(
      context: context,
      applicationName: 'Flutter 入門指北',
      applicationVersion: '0.1.1',
      applicationLegalese: 'Copyright: this is a copyright notice topically',
      applicationIcon: Image.asset('images/app_icon.png', width: 40.0, height: 40.0),
      children: <Widget>[Text('我是個比較正經(jīng)的對話框內(nèi)容...你可以隨便把我替換成任何部件义图,只要你喜歡(*^▽^*)')],
    );
  }

最后的效果:

about_dialog.gif

AboutDialog 會自帶兩個按鈕 VIEW LICENSESCLOSE减俏,VIEW LICENSES 會跳轉(zhuǎn)一個 Flutter Licenses 的網(wǎng)頁,CLOSE 會關(guān)閉碱工,至于為什么是英文的娃承,是因為我們沒有設(shè)置語言的原因奏夫,這個涉及到多語言,這邊推薦幾篇之前看過的文章历筝,如果下次有時間的話會單獨拿出來講下

英文原版多語言設(shè)置酗昼,介紹兩種方式實現(xiàn)

國人翻譯版,未持續(xù)更新第二種方式

使用插件 in18 版

這邊為了支持中文梳猪,我們做下如下的修改麻削,首先打開 pubspec.ymal 文件加入如下支持

location_01.png

get package 后給 MaterialApp 加入如下屬性,這樣就會支持中文了舔示,這里需要導(dǎo)入包 package:flutter_localizations/flutter_localizations.dart碟婆,再次運行,就會發(fā)現(xiàn)之前的英文變成中文了惕稻,當(dāng)然你也可以設(shè)置成別的語言竖共。

location_02.png

Dialog 狀態(tài)保持

假如有個需求,需要在彈出的 Dialog 顯示當(dāng)前被改變的值俺祠,然后通過按鈕可以修改這個值 公给,該如何實現(xiàn)。相信很多小伙伴都會這么認為蜘渣,通過 setState 來修改不就行了嗎淌铐,沒錯,我一開始的確這么去實現(xiàn)的蔫缸,我們先看下代碼好了腿准,增加一個 DialogState 按鈕,然后指向?qū)?yīng)的點擊事件

_showStateDialog() {
    showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) => SimpleDialog(
              title: Text('我這邊能實時修改狀態(tài)值'),
              contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
              children: <Widget>[
                Text('當(dāng)前的值是: $_count', style: TextStyle(fontSize: 18.0)),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 12.0),
                  child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
                    RaisedButton(
                      onPressed: increase,
                      child: Text('點我自增'),
                    ),
                    RaisedButton(
                      onPressed: decrease,
                      child: Text('點我自減'),
                    ),
                    RaisedButton(
                      onPressed: () => Navigator.pop(context),
                      child: Text('點我關(guān)閉'),
                    )
                  ]),
                )
              ],
            ));
  }

然后我們運行看下

dialog_state_01.gif

誒誒誒拾碌,怎么 Dialog 的值不改變呢吐葱,明明界面上的已經(jīng)修改了啊。所以說圖樣圖森破咯校翔,看下官方對 showDialog 方法的解釋吧

/// This function takes a `builder` which typically builds a [Dialog] widget.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.

糟糕透的翻譯又來了:該方法通過 builder 參數(shù)來傳入一個 Dialog 部件弟跑,dialog 下的內(nèi)容被一個「模態(tài)障礙」阻隔,buildercontext 和調(diào)用 showDialog 時候的 context 不是共享的防症,如果需要動態(tài)修改 dialog 的狀態(tài)值孟辑,需要通過 StatefulBuilder 或者自定義 dialog 繼承于 StatefulWidget 來實現(xiàn)

所以解決的方法很明確,對上面的代碼進行修改蔫敲,在外層嵌套一個 StatefulBuilder 部件

 _showStateDialog() {
    showDialog(
        context: context,
        barrierDismissible: false,
        // 通過 StatefulBuilder 來保存 dialog 狀態(tài)
        // builder 需要傳入一個 BuildContext 和 StateSetter 類型參數(shù)
        // StateSetter 有一個 VoidCallback饲嗽,修改狀態(tài)的方法在這寫
        builder: (context) => StatefulBuilder(
            builder: (context, dialogStateState) => SimpleDialog(
                  title: Text('我這邊能實時修改狀態(tài)值'),
                  contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
                  children: <Widget>[
                    Text('當(dāng)前的值是: $_count', style: TextStyle(fontSize: 18.0)),
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 12.0),
                      child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
                        RaisedButton(
                          // 通過 StatefulBuilder 的 StateSetter 來修改值
                          onPressed: () => dialogStateState(() => increase()),
                          child: Text('點我自增'),
                        ),
                        RaisedButton(
                          onPressed: () => dialogStateState(() => decrease()),
                          child: Text('點我自減'),
                        ),
                        RaisedButton(
                          onPressed: () => Navigator.pop(context),
                          child: Text('點我關(guān)閉'),
                        )
                      ]),
                    )
                  ],
                )));
  }

然后再運行下,可以看到 dialog 和界面的值保持一致了

dialog_state_02.gif

以上部分代碼查看 prompt_main.dart 文件

差不多常用彈窗和操作提示就在這了奈嘿,好好消化吧~

最后代碼的地址還是要的:

  1. 文章中涉及的代碼:demos

  2. 基于郭神 cool weather 接口的一個項目喝噪,實現(xiàn) BLoC 模式,實現(xiàn)狀態(tài)管理:flutter_weather

  3. 一個課程(當(dāng)時買了想看下代碼規(guī)范的指么,代碼更新會比較慢酝惧,雖然是跟著課上的一些寫代碼榴鼎,但是還是做了自己的修改,很多地方看著不舒服晚唇,然后就改成自己的實現(xiàn)方式了):flutter_shop

如果對你有幫助的話巫财,記得給個 Star,先謝過哩陕,你的認可就是支持我繼續(xù)寫下去的動力~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末平项,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悍及,更是在濱河造成了極大的恐慌闽瓢,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件心赶,死亡現(xiàn)場離奇詭異扣讼,居然都是意外死亡,警方通過查閱死者的電腦和手機缨叫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門椭符,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耻姥,你說我怎么就攤上這事销钝。” “怎么了琐簇?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵蒸健,是天一觀的道長。 經(jīng)常有香客問我婉商,道長纵装,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任据某,我火速辦了婚禮,結(jié)果婚禮上诗箍,老公的妹妹穿的比我還像新娘癣籽。我一直安慰自己,他們只是感情好滤祖,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布筷狼。 她就那樣靜靜地躺著,像睡著了一般匠童。 火紅的嫁衣襯著肌膚如雪埂材。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天汤求,我揣著相機與錄音俏险,去河邊找鬼严拒。 笑死,一個胖子當(dāng)著我的面吹牛竖独,可吹牛的內(nèi)容都是我干的裤唠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莹痢,長吁一口氣:“原來是場噩夢啊……” “哼种蘸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起竞膳,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤航瞭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坦辟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊侯,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年长窄,在試婚紗的時候發(fā)現(xiàn)自己被綠了滔吠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡挠日,死狀恐怖疮绷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嚣潜,我是刑警寧澤冬骚,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站懂算,受9級特大地震影響只冻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜计技,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一喜德、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垮媒,春花似錦舍悯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至它抱,卻和暖如春秕豫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背观蓄。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工混移, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祠墅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓沫屡,卻偏偏與公主長得像饵隙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沮脖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355