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