对话框
AlertDialog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| AlertDialog( title: Text("提示"), content: Text("您确定要删除当前文件吗?"), actions: <Widget>[ TextButton( child: Text("取消"), onPressed: () => Navigator.of(context).pop(), ), TextButton( child: Text("删除"), onPressed: () { Navigator.of(context).pop(true); }, ), ], );
|
现在,对话框我们已经构建好了,那么如何将它弹出来呢?还有对话框返回的数据应如何被接收呢?这些问题的答案都在showDialog()方法中。
showDialog()是Material组件库提供的一个用于弹出Material风格对话框的方法,签名如下:
该方法只有两个参数,含义见注释。该方法返回一个Future,它正是用于接收对话框的返回值:如果我们是通过点击对话框遮罩关闭的,则Future的值为null,否则为我们通过Navigator.of(context).pop(result)返回的result值
1 2 3 4 5
| Future<T?> showDialog<T>({ required BuildContext context, required WidgetBuilder builder, bool barrierDismissible = true, })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| ElevatedButton( child: Text("对话框1"), onPressed: () async { bool? delete = await showDeleteConfirmDialog1(); if (delete == null) { print("取消删除"); } else { print("已确认删除"); } }, ),
Future<bool?> showDeleteConfirmDialog1() { return showDialog<bool>( context: context, builder: (context) { return AlertDialog( title: Text("提示"), content: Text("您确定要删除当前文件吗?"), actions: <Widget>[ TextButton( child: Text("取消"), onPressed: () => Navigator.of(context).pop(), ), TextButton( child: Text("删除"), onPressed: () { Navigator.of(context).pop(true); }, ), ], ); }, ); }
|
注意:如果AlertDialog的内容过长,内容将会溢出,这在很多时候可能不是我们期望的,所以如果对话框内容过长时,可以用SingleChildScrollView将内容包裹起来。
2. SimpleDialog
SimpleDialog也是Material组件库提供的对话框,它会展示一个列表,用于列表选择的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| Future<void> changeLanguage() async { int? i = await showDialog<int>( context: context, builder: (BuildContext context) { return SimpleDialog( title: const Text('请选择语言'), children: <Widget>[ SimpleDialogOption( onPressed: () { Navigator.pop(context, 1); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: const Text('中文简体'), ), ), SimpleDialogOption( onPressed: () { Navigator.pop(context, 2); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: const Text('美国英语'), ), ), ], ); });
if (i != null) { print("选择了:${i == 1 ? "中文简体" : "美国英语"}"); } }
|
列表项组件我们使用了SimpleDialogOption组件来包装了一下,它相当于一个TextButton,只不过按钮文案是左对齐的,并且padding较小。上面示例运行后,用户选择一种语言后,控制台就会打印出它。
3. Dialog
实际上AlertDialog和SimpleDialog都使用了Dialog类。由于AlertDialog和SimpleDialog中使用了IntrinsicWidth来尝试通过子组件的实际尺寸来调整自身尺寸,这就导致他们的子组件不能是延迟加载模型的组件(如ListView、GridView 、 CustomScrollView等),如下面的代码运行后会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| Future<void> showListDialog() async { int? index = await showDialog<int>( context: context, builder: (BuildContext context) { var child = Column( children: <Widget>[ ListTile(title: Text("请选择")), Expanded( child: ListView.builder( itemCount: 30, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text("$index"), onTap: () => Navigator.of(context).pop(index), ); }, )), ], ); return UnconstrainedBox( constrainedAxis: Axis.vertical, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 280), child: Material( child: child, type: MaterialType.card, ), ), ); }, ); if (index != null) { print("点击了:$index"); } }
|
可能有些读者会惯性的以为在builder中只能返回这三者之一,其实这不是必须的!就拿Dialog的示例来举例,我们完全可以用下面的代码来替代Dialog
拓展的对话框
那如何打开一个普通风格的对话框呢(非Material风格)? Flutter 提供了一个showGeneralDialog方法,签名如下:
1 2 3 4 5 6 7 8 9 10
| Future<T?> showGeneralDialog<T>({ required BuildContext context, required RoutePageBuilder pageBuilder, bool barrierDismissible = false, String? barrierLabel, Color barrierColor = const Color(0x80000000), Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, ... })
|
下面我们自己封装一个showCustomDialog方法,它定制的对话框动画为缩放动画,并同时制定遮罩颜色为Colors.black87:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| Future<T?> showCustomDialog<T>({ required BuildContext context, bool barrierDismissible = true, required WidgetBuilder builder, ThemeData? theme, }) { final ThemeData theme = Theme.of(context, shadowThemeOnly: true); return showGeneralDialog( context: context, pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) { final Widget pageChild = Builder(builder: builder); return SafeArea( child: Builder(builder: (BuildContext context) { return theme != null ? Theme(data: theme, child: pageChild) : pageChild; }), ); }, barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.black87, transitionDuration: const Duration(milliseconds: 150), transitionBuilder: _buildMaterialDialogTransitions, ); }
Widget _buildMaterialDialogTransitions( BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return ScaleTransition( scale: CurvedAnimation( parent: animation, curve: Curves.easeOut, ), child: child, ); }
|