关于 Flutter Dialog 中动态修改 Text

因为团队需求,开始写 Flutter ,不得不说用于快速开发太方便了。

不过在一些情况下的处理逻辑和 Android 差了好多。

直接上需求效果图:

这个需求简单地说就是 对话框 中有一个 文本框 ,点击 文本框 后会弹出时间选择界面,选择后更新 文本框 文本。

Android 里是非常容易的。但是在 Flutter 中,反正我一开始连修改文本框的文本都不会,最后在同伴的帮助下解决了。特此记录下来。

显示对话框

显示对话框就容易了,直接上方法模型:

1
2
3
4
5
6
7
8
9
/// 展示添加提醒提示框
Future<void> _showAddReminderDialog() async {
await showDialog(
context: context,
builder: (BuildContext context) {
/// 对话框 Builder
}
);
}

一般而言,我们会直接在 对话框 builder 里返回一个 AlertDialog 对象,但是直接构造的 AlertDialog 是无状态的 widget ,没办法满足动态更换 Text 的需求。所以我们这里需要在 builder 里返回一个状态空间让这个 Dialog 带状态,也就是 StatefulBuilder,然后在状态空间里返回对话框 AlertDialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 展示添加提醒提示框
Future<void> _showAddReminderDialog() async {
await showDialog(
context: context,
builder: (BuildContext context) {
/// 返回状态构造器
return StatefulBuilder(
builder:
(BuildContext context, StateSetter setState) {
/// 状态空间的一个变量,当前选中的时间
int _chooseTime = DateTime.now().millisecondsSinceEpoch;
return AlertDialog(
/// 对话框参数
); // AlertDialog
}
); // StatefulBuilder
}
);
}

构造对话框

以下直接给出 AlertDialog 的对象模型,之前的显示和状态空间构造器省略。

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
AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
), // RoundedRectangleBorder
title: const Text('选择时间'),
content:InkWell(
onTap: () async {
/// 点击事件 ……
},
child: Text(
() {
DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(_chooseTime);
return '${dateTime.year}/${dateTime.month}/${dateTime.day} ${dateTime.hour}:${dateTime.minute}:${dateTime.second}';
}(),
), // Text
actions: [
FlatButton(
/// 确认按钮 ……
), // FlatButton
FlatButton(
/// 取消按钮 ……
), // FlatButton
]
)// InkWell
) // AlertDialog

搞定,在状态空间中,当需要更新当前选择的时间的时候,直接调用:

1
2
3
setState(() {
_chooseTime = date.millisecondsSinceEpoch;
});

原理

首先是时间显示:

1
2
3
4
5
6
7
Text(
() {
DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(_chooseTime);
return '${dateTime.year}/${dateTime.month}/${dateTime.day} ${dateTime.hour}:${dateTime.minute}:${dateTime.second}';
}(),
), // Text

构造 Text 对象的时候我们不直接传入要显示的内容,而是传入一个返回值为 string 的闭包方法。闭包方法中我们直接获取状态空间中 _chooseTime 变量并构造字符串。

setState 为刚刚 StatefulBuilder 的第二个参数,是一个回调对象,要传入一个方法,方法内部可以修改你要修改的变量。

当你向 setState 传入回调后,回调会先被调用,然后整个状态空间将会重新调用 build 方法,即重建整个 AlertDialog。此时的 Text 依然是获取 _chooseTime 变量,只不过我们已经通过回调改变了它的值。

完整代码(包括时间选择)

这里是项目中完整的代码,初始时间为当前日记的时间,点击添加提醒后将向系统日历添加一个日程。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/// 展示添加提醒提示框
Future<void> _showAddReminderDialog() async {
await showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
int _chooseTime =
widget.note?.time ?? DateTime.now().millisecondsSinceEpoch;
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
), // RoundedRectangleBorder
title: Text(S.of(context).add_reminder),
content: InkWell(
onTap: () async {
DateTime dateTime = DateTime.now();
if (widget.note?.time != null) {
dateTime = DateTime.fromMillisecondsSinceEpoch(
widget.note.time,
);
}
DateTime date = await showDatePicker(
context: context,
initialDate: dateTime,
firstDate: DateTime.fromMillisecondsSinceEpoch(0),
lastDate: DateTime.now().add(const Duration(days: 365)),
);

if (date != null) {
TimeOfDay time = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (time != null) {
date = date.add(Duration(
hours: time.hour,
minutes: time.minute,
));
setState(() {
_chooseTime = date.millisecondsSinceEpoch;
});
}
}
},
child: Text(
() {
DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(_chooseTime);
return '${dateTime.year}/${dateTime.month}/${dateTime.day} ${dateTime.hour}:${dateTime.minute}:${dateTime.second}';
}(),
), // Text
), // InkWell
actions: [
FlatButton(
child: Text(S.of(context).add_reminder),
onPressed: () async {
bool su = await Add2Calendar.addEvent2Cal(Event(
title: widget.note?.title ?? 'time note',
description: widget.note?.content ?? 'event',
startDate:
DateTime.fromMillisecondsSinceEpoch(_chooseTime),
endDate: DateTime.fromMillisecondsSinceEpoch(_chooseTime)
.add(const Duration(
minutes: 20,
)),
));
Navigator.pop(context, true);
},
), // FlatButton
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.pop(context, true);
},
), // FlatButton
],
); // AlertDialog
},
); // StatefulBuilder
},
);
}