Flutter 60: 圖解基本 Dialog 對話框小結

??????Dialog 在日常開發中應用廣泛,大家也對此很熟悉;小菜以前也整理過關于自定義 Dialog 的小博客,今天小菜系統的學習一下最基本的 Dialog

??????Dialog 一般不直接使用,Flutter 提供了便利的 AlertDialog / SimpleDialog / AboutDialog / CupertinoDialog / CupertinoAlertDialog 等多種對話框樣式,小菜重點嘗試前三種 Android Type Dialog;但對于自定義對話框可繼承 Dialog 進行處理;

AlertDialog

源碼分析

const AlertDialog({
    Key key,
    this.title,     // 標題內容
    this.titlePadding,  // 標題與周圍邊距
    this.titleTextStyle,    // 標題樣式
    this.content,   // 消息內容
    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),      // 消息內容與周圍邊距
    this.contentTextStyle,  // 消息內容樣式
    this.actions,   // 操作按鈕組合
    this.backgroundColor,   // 對話框背景色
    this.elevation,     // 對話框陰影
    this.semanticLabel,     // 對話框語義化標簽
    this.shape,     // 對話框形狀
}) 

??????分析源碼,AlertDialog 各個屬性都比較清楚,靈活性較高;小菜由簡易難逐漸嘗試;

案例嘗試

  1. 小菜嘗試日常最常見的 Dialog
showDialog(context: context,
    builder: (context) {
      return AlertDialog(title: Text('AlertDialog', style: TextStyle(color: Colors.blueAccent)),
          content: Text('我是 AlertDialog 對話框!'),
          actions: <Widget>[
            FlatButton(child: Text("確定"), onPressed: () => Navigator.of(context).pop()),
            FlatButton(child: Text("取消"), onPressed: () => Navigator.of(context).pop())
          ]);
    });
  1. 小菜嘗試對上述 Dialog 添加一些個性化;
    a. titleTextStylecontentTextStyle 不能改變標題和內容中已設置過的樣式;
    b. shape 為對話框樣式,如果設置為 CircleBorder 圓形背景效果時以寬高較小的尺寸為直徑;
    c. actions 按鈕個數最多可設置三個;
showDialog(context: context,
    builder: (context) {
      return AlertDialog(
          title: Text('AlertDialog', style: TextStyle(color: Colors.blueAccent)),
          titlePadding: EdgeInsets.all(20.0),
          titleTextStyle: TextStyle(color: Colors.pinkAccent, fontSize: 18.0, fontWeight: FontWeight.w600),
          content: Text('我是 AlertDialog 對話框!'),
          contentPadding: EdgeInsets.all(30.0),
          contentTextStyle: TextStyle(color: Colors.pinkAccent, fontSize: 16.0, fontWeight: FontWeight.w400),
          backgroundColor: Colors.greenAccent.withOpacity(0.7),
          elevation: 10.0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0))),
          actions: <Widget>[
            FlatButton(child: Text("確定"), onPressed: () => Navigator.of(context).pop()),
            FlatButton(child: Text("取消"), onPressed: () => Navigator.of(context).pop())
          ]);
    });
  1. 小菜嘗試 List AlertDialog
    a. Dialog 默認寬度是固定的,高度也有最大限度,若元素大小超過最大寬高則會溢出;
    b. AlertDialog 可以自由設置點擊事件,并非只有 actions 設置;
showDialog(context: context,
    builder: (context) {
      return AlertDialog(
          title: Row(children: <Widget>[
            Image.asset('images/ic_launcher.png', scale: 2.0),
            Padding(child: Text('Alert List'), padding: EdgeInsets.only(left: 12.0)) ]),
          content: ListView.builder(itemCount: 30,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(title: Text('當前 index = $index'), onTap: () => Navigator.of(context).pop(index));
              }),
          elevation: 10.0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14.0))));
    });
  1. 小菜嘗試自定義選擇對話框;
showDialog(context: context,
    builder: (context) {
      return AlertDialog(
          title: Row(children: <Widget>[
            Image.asset('images/ic_launcher.png', scale: 2.0),
            Padding(child: Text('Alert 性別選擇'), padding: EdgeInsets.only(left: 12.0)) ]),
          titleTextStyle: TextStyle(color: Colors.pinkAccent, fontSize: 18.0, fontWeight: FontWeight.w600),
          content: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
            Row(children: <Widget>[
              Expanded(child: GestureDetector(child: Container(child: Image.asset('images/icon_type_boy.png'), width: 105.0),
                      onTap: () {
                        Navigator.of(context).pop();
                        Toast.show('AlertDialog Boy!', context, duration: Toast.LENGTH_SHORT,gravity: Toast.BOTTOM);
                      })),
              Expanded(child: GestureDetector(child: Container(child: Image.asset('images/icon_type_girl.png'), width: 105.0),
                      onTap: () {
                        Navigator.of(context).pop();
                        Toast.show('AlertDialog Girl!', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM);
                      })) ]),
            Row(children: <Widget>[
              Expanded(child: Center(child: Text('男生', style: TextStyle(color: Colors.blueAccent, fontSize: 16.0, fontWeight: FontWeight.w300)))),
              Expanded(child: Center(child: Text('女生'))) ])
          ]),
          contentTextStyle: TextStyle(color: Colors.pinkAccent, fontSize: 16.0, fontWeight: FontWeight.w300),
          contentPadding: EdgeInsets.fromLTRB(24.0, 10.0, 24.0, 0.0),
          elevation: 10.0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14.0))),
          actions: <Widget>[
            FlatButton(child: Text("確定"), onPressed: () => Navigator.of(context).pop()),
            FlatButton(child: Text("取消"), onPressed: () => Navigator.of(context).pop())
          ]);
    });

SimpleDialog

源碼分析

const SimpleDialog({
    Key key,
    this.title,     // 標題內容
    this.titlePadding = const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),  // 標題與周圍邊距
    this.children,   // 消息內容
    this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),      // 消息內容與周圍邊距
    this.backgroundColor,  // 對話框背景色
    this.elevation,     // 對話框陰影
    this.semanticLabel,     // 對話框語義化標簽
    this.shape,     // 對話框形狀
})

??????分析源碼,SimpleDialogAlertDialog 要簡單,只是單獨多一個 titlePadding;消息主體默認是 List<Widget>;基本 SimpleDialog 可實現的效果 AlertDialog 均可實現;

案例嘗試

  1. 小菜嘗試最常見的選擇對話框;小菜采用了 SimpleDialogOption 選項 Widget,默認是占滿一行;
showDialog(context: context,
    builder: (context) {
      return SimpleDialog(
          title: Text('SimpleDialog', style: TextStyle(color: Colors.blueAccent)),
          children: <Widget>[
            Padding(child: Text('我是 SimpleDialog 對話框?'), padding: EdgeInsets.all(20.0)),
            SimpleDialogOption(child: Text('Yes'), onPressed: () => Navigator.pop(context)),
            SimpleDialogOption(child: Text('No'), onPressed: () => Navigator.pop(context))
          ]);
    });
  1. 小菜嘗試 List SimpleDialog;需注意內容主體為 List<Widget> 方式,使用 ListView 時要注意沖突;
showDialog(context: context,
    builder: (context) {
      return SimpleDialog(
          title: Row(children: <Widget>[
            Image.asset('images/ic_launcher.png', scale: 2.0),
            Padding(child: Text('Simple List'), padding: EdgeInsets.only(left: 12.0))
          ]),
          children: <Widget>[
            Container(height: 400.0,
                child: ListView.builder(itemCount: 30,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(title: Text('當前 index = $index'), onTap: () => Navigator.of(context).pop(index));
                    }))],
          elevation: 10.0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14.0))));
    });
  1. 小菜嘗試自定義選擇對話框;
showDialog(context: context, barrierDismissible: false,
    builder: (context) {
      return SimpleDialog(
          title: Row(children: <Widget>[
            Image.asset('images/ic_launcher.png', scale: 2.0),
            Padding(child: Text('Simple 性別選擇'), padding: EdgeInsets.only(left: 12.0))
          ]),
          children: <Widget>[
            Row(children: <Widget>[
              Expanded(child: GestureDetector(child: Container(child: Image.asset('images/icon_type_boy.png'), width: 105.0),
                      onTap: () { Toast.show('SimpleDialog Boy!', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM); })),
              Expanded(child: GestureDetector(child: Container( child: Image.asset('images/icon_type_girl.png'), width: 105.0),
                      onTap: () { Toast.show('SimpleDialog Girl!', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM); })) ]),
            Padding(padding: EdgeInsets.symmetric(vertical: 14.0)),
                child: Row(children: <Widget>[
                  Expanded(child: Center(child: Text('男生', style: TextStyle(color: Colors.blueAccent, fontSize: 16.0, fontWeight: FontWeight.w300)))),
                  Expanded(child: Center(child: Text('女生', style: TextStyle( color: Colors.pinkAccent, fontSize: 16.0, fontWeight: FontWeight.w300))))
                ])],
          elevation: 10.0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14.0))));
    });

UnconstrainedBox + SizedBox

??????Flutter 的對話框中均未提供更改寬度的屬性,高度可以自適應;小菜采用 UnconstrainedBox + SizedBox 可以實現對話框的寬度更改,首先用 UnconstrainedBox 抵消 showDialog 對寬度的限制;之后采用 SizedBox 設置對話框寬度;注意此時設置高度并沒有效果依舊自適應;對話框寬度以 SizedBox 設置的 width 為主,child 的寬度無效;

showDialog(context: context, barrierDismissible: false,
    builder: (context) {
      return UnconstrainedBox(
          constrainedAxis: Axis.vertical,
          child: SizedBox(width: 180.0, height: 180.0,
              child: AlertDialog(content: Icon(Icons.ac_unit))));
    });

AboutDialog

??????Flutter 提供了特殊的 AboutDialog,適用于應用說明或版本相關;

源碼分析

const AboutDialog({
    Key key,
    this.applicationName,       // 應用名稱
    this.applicationVersion,    // 版本說明
    this.applicationIcon,       // 應用圖標
    this.applicationLegalese,   // 法律聲明
    this.children,              // 消息內容
})

??????分析源碼可知,AboutDialog 繼承自 AlertDialog 但對于自定義內容較少,對于 applicationName / applicationVersion / applicationLegalese 僅提供字符串方式,無法調整樣式;且默認有版權和取消按鈕;

案例嘗試

??????AboutDialog 類似于系統對話框,整體效果我們無法調整,對于主體內容 children 部分,與 SimpleDialog 類似,無法延遲加載模型組件,對于 ListView 等需明確高度;

showDialog(context: context,
    barrierDismissible: false,
    builder: (context) {
      return AboutDialog(
          applicationIcon: Container(child: Image.asset('images/icon_hzw02.jpg'), width: 80.0),
          applicationName: 'Flutter Dialog',
          applicationLegalese: '所有解釋權歸本人所有!',
          applicationVersion: 'V1.5.2',
          children: <Widget>[
            Padding(padding: EdgeInsets.only(top: 10.0), child: Text('1. AboutDialog!')),
            Padding(padding: EdgeInsets.only(top: 10.0), child: Text('2. SimpleDialog!')),
            Padding(padding: EdgeInsets.only(top: 10.0), child: Text('3. AlertDialog!'))
          ]);
    });

showAboutDialog

??????Flutter 針對 AboutDialog 提供了簡易的 showAboutDialog 方法;

源碼分析

void showAboutDialog({
  @required BuildContext context,
  String applicationName,       // 應用名稱
  String applicationVersion,    // 版本說明
  Widget applicationIcon,       // 應用圖標
  String applicationLegalese,   // 法律聲明
  List<Widget> children,        // 消息內容
})

??????分析源碼,showAboutDialog 是簡化版的 AboutDialog,參數幾乎全部一致;差別在于 showDialog 方式可以設置點擊遮罩是否關閉對話框,而 showAboutDialog 不支持;

案例嘗試

showAboutDialog(context: context,
    applicationIcon: Container(child: Image.asset('images/icon_hzw02.jpg'), width: 80.0),
    applicationName: 'Flutter Dialog',
    applicationLegalese: '所有解釋權歸本人所有!',
    applicationVersion: 'V1.5.2',
    children: <Widget>[
      Padding(padding: EdgeInsets.only(top: 10.0), child: Text('1. AboutDialog!')),
      Padding(padding: EdgeInsets.only(top: 10.0), child: Text('2. SimpleDialog!')),
      Padding(padding: EdgeInsets.only(top: 10.0), child: Text('3. AlertDialog!'))
    ]);

showDialog

源碼分析

Future<T> showDialog<T>({
  @required BuildContext context,
  bool barrierDismissible = true,   // 遮罩層點擊是否關閉對話框
  @Deprecated(
    'Instead of using the "child" argument, return the child from a closure '
    'provided to the "builder" argument. This will ensure that the BuildContext '
    'is appropriate for widgets built in the dialog.'
  ) Widget child,
  WidgetBuilder builder,
})

??????分析源碼,showDialog 采用 builder 方式取代 child 方式;而實際上 showDialog 是對 showGeneralDialog 的封裝,默認的遮罩層顏色和漸進漸出的動畫效果;

showGeneralDialog

源碼分析

Future<T> showGeneralDialog<T>({
  @required BuildContext context,
  @required RoutePageBuilder pageBuilder,   // 對話框內部繪制
  bool barrierDismissible,      // 遮罩層點擊是否關閉對話框
  String barrierLabel,      // 語義化標簽
  Color barrierColor,       // 遮罩層顏色
  Duration transitionDuration,      // 動畫持續時長
  RouteTransitionsBuilder transitionBuilder,    // 動畫過程
})

??????分析源碼,showGeneralDialog 提供了更豐富的對話框設計;而實際也是對 Navigator.push 的封裝;

案例嘗試

??????小菜重現以前博客中實現的簡易對話框:由底部彈出且透明度由 0.0 到 1.0;測試 barrierColor 進入和退出時都是漸變符合動畫效果,與采用 Navigator 打開頁面動畫方式不同;

showGeneralDialog(context: context,
    pageBuilder: (buildContext, _, __) {
      return Center(child: Container(
              height: 200.0, width: 200.0,
              decoration: BoxDecoration(color: Colors.greenAccent, borderRadius: BorderRadius.circular(5.0)),
              child: Icon(Icons.ac_unit, color: Colors.white)));
    },
    barrierDismissible: false,
    barrierColor: Colors.pink.withOpacity(0.2),
    transitionDuration: Duration(milliseconds: 1500),
    transitionBuilder: (context, animation, secondaryAnimation, child) {
      return SlideTransition(
          position: Tween<Offset>(begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0)).animate(CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn)),
          child: FadeTransition(opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animation, curve: Curves.linear)),
              child: child));
    });

??????雖然我們經常自定義 Dialog,但還是需要對系統基礎的 Dialog 有所認知;以上是小菜的測試過程,如有錯誤請多多指導!

來源: 阿策小和尚

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容