前言
對話框本質上也是UI布局,通常一個對話框會包含標題、內容,以及一些操作按鈕,為此,Material庫中提供了一些現成的對話框組件來用於快速的構建出一個完整的對話框。
接口描述
// 1. AlertDialog
const AlertDialog({
Key key,
// 對話框組件標題
this.title,
// 標題填充
this.titlePadding,
// 標題文本樣式
this.titleTextStyle,
// 對話框內容組件
this.content,
// 內容的填充
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
// 內容文本樣式
this.contentTextStyle,
// 對話框操作按鈕組
this.actions,
// 對話框背景色
this.backgroundColor,
// 對話框的陰影
this.elevation,
// 對話框語義化標簽(用於讀屏軟件)
this.semanticLabel,
// 對話框外形
this.shape,
}) : assert(contentPadding != null),
super(key: key);
代碼示例
// 對話框詳解(dialog)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// 1. AlertDialog,消息對話框
Future<bool> showDeleteConfirmDialog(context){
return showDialog<bool>(
context: context,
//點擊對話框barrier(遮罩)時是否關閉它
barrierDismissible: false,
builder: (context){
return AlertDialog(
title: Text("提示"),
content: Text("您確定要刪除當前文件嗎?"),
actions: <Widget>[
FlatButton(
child: Text("取消"),
// 關閉對話框
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text("確定"),
onPressed: (){
// 關閉對話框並返回true
Navigator.of(context).pop(true);
},
),
],
);
}
);
}
// 2. SimpleDialog,列表對話框
Future<void> changeLanguageDialog(context) async{
int i = await showDialog<int>(
context: context,
builder: (context){
return SimpleDialog(
title: const Text("請選擇語言"),
children: <Widget>[
SimpleDialogOption(
onPressed: (){
// 返回1
Navigator.pop(context, 1);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0),
child: const Text("中文簡體"),
),
),
SimpleDialogOption(
onPressed: (){
// 返回2
Navigator.pop(context, 2);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text("美國英語"),
),
),
],
);
}
);
if(i != null){
print("選擇了:${i == 1 ? "中文簡體" : "美國英語"}");
}
}
// 3. Dialog,對話框
Future<void> showListDialog(context) async{
int index = await showDialog(
context: context,
builder: (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),
);
},
),
)
],
);
//使用AlertDialog會報錯
// 實際上AlertDialog和SimpleDialog都使用了Dialog類。由於AlertDialog和SimpleDialog中使用了IntrinsicWidth來嘗試通過子組件的實際尺寸來調整自身尺寸,
// 這就導致他們的子組件不能是延遲加載模型的組件(如ListView、GridView 、 CustomScrollView等)
// return AlertDialog(content: child);
return Dialog(child: child);
}
);
if (index != null) {
print("點擊了:$index");
}
}
// 4. showGeneralDialog,自定義非Material風格對話框
Future<T> customDialog<T>({
@required BuildContext context,
bool barrierDismissible = true,
WidgetBuilder builder,
}){
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,
);
}
Future<bool> showCustomDialog(context){
return customDialog<bool>(
context: context,
//點擊對話框barrier(遮罩)時是否關閉它
barrierDismissible: false,
builder: (context){
return AlertDialog(
title: Text("提示"),
content: Text("您確定要刪除當前文件嗎?"),
actions: <Widget>[
FlatButton(
child: Text("取消"),
// 關閉對話框
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text("確定"),
onPressed: (){
// 關閉對話框並返回true
Navigator.of(context).pop(true);
},
),
],
);
}
);
}
// 5. 對話框狀態管理
Future<bool> showDeleteConfirmDialog1(context) {
bool _withTree = false;
return showDialog<bool>(
context: context,
builder: (context){
return AlertDialog(
title: Text("提示"),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("您確定要刪除當前文件嗎?"),
Row(
children: <Widget>[
Text("同時刪除子目錄"),
// Checkbox(
// // 使用Checkbox組件
// value: _withTree,
// onChanged: (bool value){
// // 此時context為對話框UI的根Element,我們
// // 直接將對話框UI對應的Element標記為dirty
// (context as Element).markNeedsBuild();
// _withTree = !_withTree;
// },
// )
// 通過Builder來獲得構建Checkbox的`context`,
// 這是一種常用的縮小`context`范圍的方式
Builder(
builder: (BuildContext context) {
return Checkbox(
value: _withTree,
onChanged: (bool value) {
(context as Element).markNeedsBuild();
_withTree = !_withTree;
},
);
},
),
],
)
],
),
actions: <Widget>[
FlatButton(
child: Text("取消"),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text("刪除"),
onPressed: (){
// 執行刪除操作
Navigator.of(context).pop(_withTree);
},
)
],
);
}
);
}
// 6. 底部菜單列表
Future<int> _showModalBottomSheet(context){
return showModalBottomSheet<int>(
context: context,
builder: (BuildContext context){
return ListView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index){
return ListTile(
title: Text("$index"),
onTap: () => Navigator.of(context).pop(index),
);
}
);
}
);
}
// 7. 全屏菜單列表
PersistentBottomSheetController<int> _showBottomSheet(context){
return showBottomSheet<int>(
context: context,
builder: (BuildContext context){
return ListView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index){
return ListTile(
title: Text("$index"),
onTap: (){
print("$index");
Navigator.of(context).pop();
}
);
}
);
}
);
}
// 8. Loading框,通過showDialog+AlertDialog實現
showLoadingDialog(context) {
showDialog(
context: context,
// 點擊遮罩不關閉對話框
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(top: 26),
child: Text("正在加載,請稍后..."),
)
],
),
);
}
);
}
// 9. 自定義對話框長度
// 只使用SizedBox或ConstrainedBox是不行的,原因是showDialog中已經給對話框設置了寬度限制,可以使用UnconstrainedBox先抵消showDialog對寬度的限制,然后再使用SizedBox指定寬度。
showCustomLoadingDialog(context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return UnconstrainedBox(
constrainedAxis: Axis.vertical,
child: SizedBox(
width: 280,
child: AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(top: 26.0),
child: Text("正在加載,請稍后..."),
)
],
),
),
),
);
}
);
}
// 10. Material風格的日歷選擇器
Future<DateTime> _showDatePicker(context) {
var date = DateTime.now();
return showDatePicker(
context: context,
initialDate: date,
firstDate: date,
lastDate: date.add(
// 未來30天可選
Duration(days: 30),
)
);
}
// 11. iOS風格的日歷選擇器
Future<DateTime> _showDatePicker2(context) {
var date = DateTime.now();
return showCupertinoModalPopup(
context: context,
builder: (ctx) {
return SizedBox(
height: 200,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
minimumDate: date,
maximumDate: date.add(
Duration(days: 30),
),
maximumYear: date.year + 1,
onDateTimeChanged: (DateTime value) {
print(value);
},
),
);
},
);
}
class DialogWidgetRoute extends StatefulWidget{
_DialogWidgetRouteState createState() => _DialogWidgetRouteState();
}
class _DialogWidgetRouteState extends State<DialogWidgetRoute>{
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text("對話框"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// AlertDialog
FlatButton(
color: Colors.green,
highlightColor: Colors.green,
splashColor: Colors.red,
child: Text("AlertDialog"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
onPressed: () async{
// 彈出對話框並等待其關閉
bool delete = await showDeleteConfirmDialog(context);
if(delete == null){
print("取消刪除");
} else{
print("確認刪除");
}
},
),
// SimpleDialog
FlatButton(
color: Colors.green,
highlightColor: Colors.green,
splashColor: Colors.red,
child: Text("SimpleDialog"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
onPressed: () async{
changeLanguageDialog(context);
},
),
// Dialog
FlatButton(
color: Colors.green,
highlightColor: Colors.green,
splashColor: Colors.red,
child: Text("Dialog"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
onPressed: () async{
showListDialog(context);
},
),
// customDialog
FlatButton(
color: Colors.green,
highlightColor: Colors.green,
splashColor: Colors.red,
child: Text("customDialog"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
onPressed: () async{
// 彈出對話框並等待其關閉
bool delete = await showCustomDialog(context);
if(delete == null){
print("自定義取消刪除");
} else{
print("自定義確認刪除");
}
},
),
// 對話框狀態管理
FlatButton(
color: Colors.green,
highlightColor: Colors.green,
splashColor: Colors.red,
child: Text("對話框狀態管理"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
onPressed: () async{
// 彈出對話框並等待其關閉
bool delete = await showDeleteConfirmDialog1(context);
if(delete == null){
print("取消刪除");
} else{
print("確認刪除");
}
},
),
// 底部菜單列表
RaisedButton(
child: Text("底部菜單列表"),
onPressed: () async{
int type = await _showModalBottomSheet(context);
print(type);
},
),
// Loading框
RaisedButton(
child: Text("Loading框"),
onPressed: () async{
showLoadingDialog(context);
},
),
// 自定義對話框長度
RaisedButton(
child: Text("自定義對話框長度"),
onPressed: () async{
showCustomLoadingDialog(context);
},
),
// Material風格的日歷選擇器
RaisedButton(
child: Text("Material風格的日歷選擇器"),
onPressed: () async{
_showDatePicker(context);
},
),
// iOS風格的日歷選擇器
RaisedButton(
child: Text("iOS風格的日歷選擇器"),
onPressed: () async{
_showDatePicker2(context);
},
),
],
),
),
);
}
}
總結
對話框最終都是由showGeneralDialog方法打開的,直接調用Navigator的push方法打開了一個新的對話框路由_DialogRoute,然后返回了push的返回值。可見對話框實際上正是通過路由的形式實現的,這也是為什么我們可以使用Navigator的pop 方法來退出對話框的原因。