【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 各个属性都比较清楚,灵活性较高;和尚由简易难逐渐尝试;

案例尝试

  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;基本 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 方式,使用 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 有所认知;以上是和尚的测试过程,如有错误请多多指导!

来源:阿策小和尚