柚子快報激活碼778899分享:flutter 彈窗之系列一
柚子快報激活碼778899分享:flutter 彈窗之系列一
自定義不受Navigator影響的彈窗
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _dialogController1() {
DialogController alert = DialogController.alert(
title: "Title",
subTitle: "SubTitle",
onCancel: () {
debugPrint("alert cancel");
},
run: () async {
// 一些耗時操作
return true;
},
);
alert.show(context);
}
void _dialogController2(){
DialogController loadingAlert = DialogController.loadingAlert();
loadingAlert.showWithTimeout(context, timeout: 10);
// await 其他耗時操作
loadingAlert.close();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
_dialogController1();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
GestureDetector(
onTap: () {
_dialogController2();
},
child: const Text(
'\n點擊顯示彈窗二\n',
),
),
],
)),
);
}
}
class BaseDialog {
BaseDialog(this._barrierDismissible, this._alignment);
/// 點擊背景是否關(guān)閉彈窗
final bool _barrierDismissible;
final AlignmentGeometry _alignment;
/// 頁面狀態(tài),用來做動畫判斷
bool _isCloseState = true;
/// 動畫時長
final _milliseconds = 240;
/// 初始化dialog的內(nèi)容
/// [isClose]用來標(biāo)識動畫的狀態(tài)
/// [milliseconds]用來標(biāo)識動畫時長
initContentView(
Widget Function(BuildContext context, bool isClose, int milliseconds)
builder) {
_overlayEntry = OverlayEntry(
builder: (context) {
return Stack(
alignment: _alignment,
children:
// 背景
Positioned.fill(
child: GestureDetector(
onTap: () {
// 點擊背景關(guān)閉頁面
if (_barrierDismissible) close();
},
child: AnimatedOpacity(
opacity: _isCloseState ? 0.0 : 1,
duration: Duration(milliseconds: _milliseconds),
child: Container(
color: Colors.black.withOpacity(0.5),
),
),
),
),
builder(context, _isCloseState, _milliseconds),
],
);
},
);
}
late OverlayEntry _overlayEntry;
bool _isPop = true;
/// 顯示彈窗
/// 小等于0不設(shè)置超時
void show(BuildContext context, int timeout) async {
//顯示彈窗
Overlay.of(context).insert(_overlayEntry);
// 稍微延遲一下,不然動畫不動
await Future.delayed(const Duration(milliseconds: 10));
_isCloseState = false;
// 重新build啟動動畫
_overlayEntry.markNeedsBuild();
_isPop = true;
// 啟動計時器,timeout秒后執(zhí)行關(guān)閉操作
if (timeout > 0) {
Future.delayed(Duration(seconds: timeout), () => close());
}
}
/// 關(guān)閉彈窗
Future
if (_isPop) {
_isPop = false;
_isCloseState = true;
// 重新build啟動動畫
_overlayEntry.markNeedsBuild();
// 等待動畫結(jié)束后再移除涂層
await Future.delayed(Duration(milliseconds: _milliseconds));
_overlayEntry.remove();
onClose();
}
}
void Function() onClose = () {};
}
class DialogController {
DialogController(this._baseDialog);
final BaseDialog _baseDialog;
/// 關(guān)閉彈窗
close() {
_baseDialog.close();
}
/// 顯示彈窗
show(BuildContext context) {
_baseDialog.show(context, 0);
}
/// 顯示一個默認(rèn)帶超時的彈窗
/// 小等于0不設(shè)置超時
void showWithTimeout(BuildContext context, {int timeout = 20}) {
_baseDialog.show(context, timeout);
}
/// 創(chuàng)造一個普通樣式的alert彈窗
/// 它顯示在屏幕中央,具有一個標(biāo)題和內(nèi)容描述文本,
/// [onBarrierTap]當(dāng)點擊背景時觸發(fā)
factory DialogController.alert({
required String title,
required String subTitle,
bool barrierDismissible = true,
Future
void Function()? onCancel,
}) {
final dialog = BaseDialog(
barrierDismissible,
AlignmentDirectional.center,
);
if (onCancel != null) {
dialog.onClose = onCancel;
}
dialog.initContentView((context, isClose, int milliseconds) {
return AnimatedOpacity(
opacity: isClose ? 0.0 : 1,
duration: Duration(milliseconds: milliseconds),
child: AlertDialog(
title: Text(title),
content: Text(subTitle),
actions: [
FilledButton.tonal(
onPressed: () {
dialog.close();
},
child: const Text("取消"),
),
FilledButton(
onPressed: () async {
if (run != null) {
final r = await run();
if (r) dialog.close();
} else {
dialog.close();
}
},
child: const Text("確認(rèn)"),
)
],
),
);
});
return DialogController(dialog);
}
factory DialogController.loadingAlert({
String? title,
String? subTitle,
}) {
final dialog = BaseDialog(
false,
AlignmentDirectional.center,
);
dialog.initContentView((context, isClose, int milliseconds) {
return AnimatedOpacity(
opacity: isClose ? 0.0 : 1,
duration: Duration(milliseconds: milliseconds),
child: AlertDialog(
title: Text(title ?? "正在加載"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 3,
),
), // 添加一個加載指示器
const SizedBox(height: 16),
Text(subTitle ?? '請等待...'), // 提示用戶等待
],
),
),
);
});
return DialogController(dialog);
}
}
系統(tǒng)Dialog的使用
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _showDialog() {
showDialog(
context: context,
barrierColor: Colors.transparent, //設(shè)置透明底色,自定義也可能會用到
builder: (BuildContext context) {
return AlertDialog(
title: const Text("測試標(biāo)題"),
content: const Text("測試內(nèi)容"),
actions: [
TextButton(
onPressed: () {},
child: const Text('確認(rèn)'),
),
TextButton(
onPressed: () {},
child: const Text('取消'),
),
],
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
定制Dialog
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _showDialog() {
showDialog(
context: context,
barrierColor: Colors.transparent, //設(shè)置透明底色
builder: (BuildContext context) {
return const DialogView(
title: "測試標(biāo)題",
message: "測試內(nèi)容",
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
//這個彈層一般是通過 showDialog 彈出,實際上相當(dāng)于跳轉(zhuǎn)了一個新界面,因此返回需通過 Navigator pop回去
class DialogView extends Dialog {
final String title;
final String message;
const DialogView({Key? key, required this.title, required this.message})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Container(
width: 100,
height: 150,
color: Colors.black.withOpacity(0.7),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(title,
style: const TextStyle(color: Colors.white, fontSize: 14.0)),
Text(message,
style: const TextStyle(color: Colors.white, fontSize: 14.0)),
TextButton(
onPressed: () {
//showDialog相當(dāng)于push,因此自己返回需要pop
Navigator.pop(context);
},
child: const Text('返回'),
),
],
),
),
),
);
}
}
獲取組件偏移量
//組件渲染完成之后,可以通過組價你的context參數(shù),間接獲取組件的偏移量
RenderBox box = context.findRenderObject() as RenderBox;
final local = box.localToGlobal(Offset.zero);
debugPrint("組件偏移量:$local");
DropdownButton
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
String? selectValue;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: DropdownButton(
hint: const Text("請選擇您要的號碼:"),
items: getItems(),
value: selectValue,
onChanged: (value) {
debugPrint(value);
setState(() {
selectValue = value;
});
},
)
),
);
}
List
List
items.add(const DropdownMenuItem(child: Text("AA"), value: "11"));
items.add(const DropdownMenuItem(child: Text("BB"), value: "22",));
items.add(const DropdownMenuItem(child: Text("CC"), value: "33",));
items.add(const DropdownMenuItem(child: Text("DD"), value: "44",));
items.add(const DropdownMenuItem(child: Text("EE"), value: "55",));
return items;
}
}
底部彈窗BottomSheet
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _showDialog(){
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min, // 設(shè)置最小的彈出
children:
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text("Camera"),
onTap: () async {
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text("Gallery"),
onTap: () async {
},
),
],
);
}
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
PopupMenuButton
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
var items =
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.greenAccent,
actions:
PopupMenuButton
itemBuilder: (BuildContext context) {
return _getItemBuilder2();
},
icon: const Icon(Icons.access_alarm),
onSelected: (value) {
debugPrint(value);
},
onCanceled: () {},
offset: const Offset(200, 100),
)
],
),
body: const Center(),
);
}
List
return items
.map((item) => PopupMenuItem
value: item,
child: Text(item),
))
.toList();
}
List
return
const PopupMenuItem
value: "1",
child: ListTile(
leading: Icon(Icons.share),
title: Text('分享'),
),
),
const PopupMenuDivider(), //分割線
const PopupMenuItem
value: "2",
child: ListTile(
leading: Icon(Icons.settings),
title: Text('設(shè)置'),
),
),
];
}
}
明確 Flutter 中 dialog 的基本特性
Flutter 中 dialog 實際上是一個由 route 直接切換顯示的頁面,所以使用 Navigator.of(context) 的 push、pop(xx) 方法進(jìn)行顯示、關(guān)閉、返回數(shù)據(jù)Flutter 中有兩種風(fēng)格的 dialog
showDialog() 啟動的是 material 風(fēng)格的對話框showCupertinoDialog() 啟動的是 ios 風(fēng)格的對話框Flutter 中有兩種樣式的 dialog
SimpleDialog 使用多個 SimpleDialogOption 為用戶提供了幾個選項AlertDialog 一個可選標(biāo)題 title 和一個可選列表的 actions 選項
?showDialog?方法講解
Future
@required BuildContext context,
bool barrierDismissible = true,
@Deprecated(
'Instead of using the "child" argument, return the child from a closure '
'provided to the "builder" argument. This will ensure that the BuildContext '
'is appropriate for widgets built in the dialog.'
) Widget child,
WidgetBuilder builder,
}) {
.......
}
context 上下文對象barrierDismissible 點外面是不是可以關(guān)閉,默認(rèn)是 true 可以關(guān)閉的builder 是 widget 構(gòu)造器FlatButton 標(biāo)準(zhǔn) AlertDialog 中的按鈕必須使用這個類型Navigator.of(context).pop(); 對話框內(nèi)關(guān)閉對話框
?AlertDialog
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _showDialog() {
// 定義對話框
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: const Text("這里是測試標(biāo)題"),
actions:
GestureDetector(
child: const Text("刪除"),
onTap: () {
debugPrint("刪除");
Navigator.of(context).pop();
},
),
GestureDetector(
child: const Text("取消"),
onTap: () {
debugPrint("取消");
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
自定義對話框
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
var num = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return TestDialog();
});
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
)));
}
}
class TestDialog extends StatefulWidget {
@override
State
return TestDialogState();
}
}
class TestDialogState extends State
var num = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(child: Container(
color: Colors.greenAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children:
Text(
num.toString(),
style: const TextStyle(decoration: TextDecoration.none),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children:
GestureDetector(
child: Text("+"),
onTap: () {
setState(() {
num++;
});
},
),
GestureDetector(
child: Text("-"),
onTap: () {
setState(() {
num--;
});
},
),
],
),
],
),
width: 100,
height: 200,
),));
}
}
SimpleDialog
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void _showDialog() {
// 定義對話框
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: new Text("SimpleDialog"),
children:
new SimpleDialogOption(
child: new Text("SimpleDialogOption One"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption One");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Two"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Two");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Three"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Three");
},
),
],
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
自定義ios風(fēng)格對話框
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State
}
class _MyHomePageState extends State
void showCupertinoDialog() {
var dialog = CupertinoAlertDialog(
content: Text(
"你好,我是你蘋果爸爸的界面",
style: TextStyle(fontSize: 20),
),
actions:
CupertinoButton(
child: Text("取消"),
onPressed: () {
Navigator.pop(context);
},
),
CupertinoButton(
child: Text("確定"),
onPressed: () {
Navigator.pop(context);
},
),
],
);
showDialog(context: context, builder: (_) => dialog);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onTap: () {
showCupertinoDialog();
},
child: const Text(
'\n點擊顯示彈窗一\n',
),
),
),
);
}
}
自定義對話框注意事項
自定義的 dialog 要是太長了超過屏幕長度了,請在外面加一個可以滾動的 SingleChildScrollView自定義的 dialog 要是有 ListView 的話,必須在最外面加上一個確定寬度和高度的 Container,要不會報錯,道理和上面的那條一樣的
案例?切換到分支 flutter_custom_widget
柚子快報激活碼778899分享:flutter 彈窗之系列一
參考閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。