Flutter dialog (1) - showDialog的讲解
文章目录
在应用开发中,或多或少都会遇到需要弹框的问题, 比如:需要用户确认,需要输入一些信息等等的问题,这就要用到 dialog 相关的概念了
而在 flutter 中,所有可以看见的都是 Widget,dialog 也不例外
不过和 android 或 iOS 中不同的一点是,Flutter 中 dialog 不是一个单独的类,而是一个可以由你自定义的 Widget
写在前面
首先为了方便,我定义了一个简单的方法用于构建按钮
1 Widget buildButton(
2 String text,
3 Function onPressed, {
4 Color color = Colors.white,
5 }) {
6 return FlatButton(
7 color: color,
8 child: Text(text),
9 onPressed: onPressed,
10 );
11 }
showDialog
dialog 的方法签名是这样的
其中 context 和 builder 是必传项
builder 需要返回一个 Widget,这个 Widget 会被作为 dialog 展示在页面上
比如我简单的写了一个这个方法
1 showDialog(
2 context: context,
3 builder: (ctx) {
4 return Center(
5 child: Column(
6 mainAxisSize: MainAxisSize.min,
7 children: <Widget>[
8 buildButton("返回1", () {}),
9 buildButton("返回2", () {}),
10 ],
11 ),
12 );
13 },
14 );
当我调用这个方法时,会得到这样的样式
这个就是最简单的方法,然后点击外部,dialog 会消失
添加关闭时的返回值
接着我给按钮添加具体的事件
修改代码为以下的样子
1_showDialog() async {
2 var result = await showDialog(
3 context: context,
4 builder: (ctx) {
5 return Center(
6 child: Column(
7 mainAxisSize: MainAxisSize.min,
8 children: <Widget>[
9 buildButton("返回1", () => Navigator.of(context).pop(1)),
10 buildButton("返回2", () => Navigator.pop(context, 2)),
11 ],
12 ),
13 );
14 },
15 );
16
17 print("result = $result");
18 }
然后分别点击 1 2 和外部让 dialog 消失 会得到以下的结果
不过这个只能让 dialog 显示固定的内容,如果你的 dialog 有内容变化,则使用这个方式就不行了,哪怕是调用 setState 也不会发生变化,这个是因为外部 State 的状态变化不会影响到 dialog 的内容,因为 dialog 是附着至 app 根部的,而不是附着于页面
结合 StatefulWidget 使用
所以我们 dialog 中也可以使用 StatefulWidget,如同一个页面一样,只是这个页面可能不是全屏的
我定义了一个简单的 CounterWidget
1
2class CounterWidget extends StatefulWidget {
3 @override
4 _CounterWidgetState createState() => _CounterWidgetState();
5}
6
7class _CounterWidgetState extends State<CounterWidget> {
8 var _counter = 0;
9
10 @override
11 Widget build(BuildContext context) {
12 return Center(
13 child: Column(
14 mainAxisSize: MainAxisSize.min,
15 children: <Widget>[
16 Material(
17 child: Container(
18 width: 100,
19 height: 100,
20 child: Text(
21 _counter.toString(),
22 style: TextStyle(fontSize: 40),
23 ),
24 alignment: Alignment.center,
25 ),
26 color: Colors.white,
27 ),
28 buildButton("+1", () => setState(() => _counter++)),
29 buildButton("-1", () => setState(() => _counter--)),
30 ],
31 ),
32 );
33 }
34}
并且调用
1showDialog(context: context, builder: (ctx) => CounterWidget());
这里可以看到,一个带状态的控件也是可以被展示在 dialog 中的
结合 StatefulBuilder
在 flutter 中有一个类,叫 StatefulBuilder
这个类的 builder 构造中会给一个 state,这个 state 是一个方法,返回 void,传入参数是一个方法,听起来很绕
大概是这样用
1var statefulBuilder = StatefulBuilder(
2 builder: (ctx, state) {
3 state(() {});
4 return Container();
5 },
6);
看起来和 setState 很像
这里我模拟一个 progress 的变化,不过这个进度是由外部传入的
1_showDialogWithStatefulBuilder() {
2 var progress = 0.0;
3 StateSetter ss;
4 Timer.periodic(Duration(milliseconds: 300), (timer) {
5 progress += 0.1;
6 if (ss != null) {
7 ss(() {});
8 }
9 if (progress >= 1) {
10 timer.cancel();
11 ss = null;
12 }
13 });
14 var sb = StatefulBuilder(
15 builder: (ctx, state) {
16 ss = state;
17 return Center(
18 child: Container(
19 height: 40,
20 child: LinearProgressIndicator(
21 backgroundColor: Colors.white,
22 value: progress,
23 ),
24 ),
25 );
26 },
27 );
28 showDialog(context: context, builder: (ctx) => sb);
29 }
这里只是简单的演示一个用法,实际应用中,进度条应该是可以多处复用的,应该使用 StatefulWidget 进行复用,而不是简易的使用 StatefulBuilder 来做这件事情,并且,应该在构建时传入 stream 并且监听 stream 为宜,而不应该使用这种 Timer 的形式
StatefulBuilder 应该用于弹出布局很特殊不太可能复用于其他地方的情况
使用 iOS 风格
有的同学可能要问了,你这演示都是 MD 风格的,我需要的是苹果风格的, 怎么办?
在 flutter 中,如果你需要 iOS 风格的,只需要使用 Cupertino 组件即可
1void showCupertinoDialog() {
2 var dialog = CupertinoAlertDialog(
3 content: Text(
4 "你好,我是你苹果爸爸的界面",
5 style: TextStyle(fontSize: 20),
6 ),
7 actions: <Widget>[
8 CupertinoButton(
9 child: Text("取消"),
10 onPressed: () {
11 Navigator.pop(context);
12 },
13 ),
14 CupertinoButton(
15 child: Text("确定"),
16 onPressed: () {
17 Navigator.pop(context);
18 },
19 ),
20 ],
21 );
22
23 showDialog(context: context, builder: (_) => dialog);
24 }
带输入框的 dialog
1 showHasInputDialog() {
2 var widget = Center(
3 child: Container(
4 height: 40,
5 width: double.infinity,
6 child: Material(
7 child: TextField(),
8 ),
9 ),
10 );
11 showDialog(context: context, builder: (_) => widget);
12 }
根据软键盘自动变化位置
之前的输入框有一些问题,如果你的弹窗在底部,则弹出的输入框可能会被挡住
这里需要另一个方法来实现
1import 'package:flutter/material.dart';
2import 'dart:ui' as ui;
3
4class InputDialog extends StatefulWidget {
5 @override
6 _InputDialogState createState() => _InputDialogState();
7}
8
9class _InputDialogState extends State<InputDialog> with WidgetsBindingObserver {
10 @override
11 void initState() {
12 super.initState();
13 WidgetsBinding.instance.addObserver(this);
14 }
15
16 @override
17 void dispose() {
18 WidgetsBinding.instance.removeObserver(this);
19 super.dispose();
20 }
21
22 @override
23 void didChangeMetrics() {
24 super.didChangeMetrics();
25 if (this.mounted) setState(() {});
26 }
27
28 @override
29 Widget build(BuildContext context) {
30 var mediaQueryData = MediaQueryData.fromWindow(ui.window);
31 return AnimatedContainer(
32 color: Colors.transparent,
33 duration: const Duration(milliseconds: 300),
34 padding: EdgeInsets.only(bottom: mediaQueryData.viewInsets.bottom),
35 child: Material(child: TextField()),
36 alignment: Alignment.center,
37 );
38 }
39}
定义一个 dialog 类,然后监听窗口的变化
然后在变化的时候动态的修改 padding,以达到输入框永远在界面中心的目的
后记
完整代码 github
第一篇主要讲了 showDialog 方法的一些使用方法和建议,以上