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

20190327134903.png

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    );

当我调用这个方法时,会得到这样的样式

20190327140047.png

这个就是最简单的方法,然后点击外部,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 消失 会得到以下的结果

20190327140439.png

不过这个只能让 dialog 显示固定的内容,如果你的 dialog 有内容变化,则使用这个方式就不行了,哪怕是调用 setState 也不会发生变化,这个是因为外部 State 的状态变化不会影响到 dialog 的内容,因为 dialog 是附着至 app 根部的,而不是附着于页面

20190327141927.png

结合 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());

Kapture 2019-03-27 at 14.26.15.gif

这里可以看到,一个带状态的控件也是可以被展示在 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  }

Kapture 2019-03-27 at 14.49.52.gif

这里只是简单的演示一个用法,实际应用中,进度条应该是可以多处复用的,应该使用 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  }

20190327145558.png

带输入框的 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  }

20190327150012.png

根据软键盘自动变化位置

之前的输入框有一些问题,如果你的弹窗在底部,则弹出的输入框可能会被挡住

这里需要另一个方法来实现

 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,以达到输入框永远在界面中心的目的

Kapture 2019-03-27 at 15.16.40.gif

后记

完整代码

第一篇主要讲了 showDialog 方法的一些使用方法和建议,以上