【Flutter 專題】60 圖解基本 Dialog 對話框小結
- 2019 年 10 月 4 日
- 筆記
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 各個屬性都比較清楚,靈活性較高;和尚由簡易難逐漸嘗試;
案例嘗試
- 和尚嘗試日常最常見的 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()) ]); });
- 和尚嘗試對上述 Dialog 添加一些個性化; a. titleTextStyle 和 contentTextStyle 不能改變標題和內容中已設置過的樣式; 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()) ]); });
- 和尚嘗試 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)))); });
- 和尚嘗試自定義選擇對話框;
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, // 對話框形狀 })
分析源碼,SimpleDialog 比 AlertDialog 要簡單,只是單獨多一個 titlePadding;消息主體默認是 List;基本 SimpleDialog 可實現的效果 AlertDialog 均可實現;
案例嘗試
- 和尚嘗試最常見的選擇對話框;和尚採用了 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)) ]); });
- 和尚嘗試 List SimpleDialog;需注意內容主體為 List 方式,使用 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)))); });
- 和尚嘗試自定義選擇對話框;
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 有所認知;以上是和尚的測試過程,如有錯誤請多多指導!
來源:阿策小和尚