Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能
Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能
目录
Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能
一、简单介绍
二、PopupDialog
三、简单案例实现
四、关键代码
一、简单介绍
Flutter 是一款开源的 UI 软件开发工具包,由 Google 开发和维护。它允许开发者使用一套代码同时构建跨平台的应用程序,包括移动设备(iOS 和 Android)、Web 和桌面平台(Windows、macOS 和 Linux)。
Flutter 使用 Dart 编程语言,它可以将代码编译为 ARM 或 Intel 机器代码以及 JavaScript,从而实现快速的性能。Flutter 提供了一个丰富的预置小部件库,开发者可以根据自己的需求灵活地控制每个像素,从而创建自定义的、适应性强的设计,这些设计在任何屏幕上都能呈现出色的外观和感觉。
二、PopupDialog
PopupDialog
是一个自定义的 Flutter 弹出对话框工具类。它通过 Overlay
实现,可以灵活地在应用中显示和移除对话框。类中包含一个全局的 navigatorKey
,用于获取当前的 OverlayState
,从而确保对话框能够正确显示。show
方法用于显示对话框,支持自定义标题、按钮文本和点击事件回调。remove
方法用于移除当前显示的对话框。对话框的 UI 包括一个半透明的遮罩和一个居中的对话框主体,主体内包含标题和两个按钮。使用时,只需调用 show
和 remove
方法即可控制对话框的显示和隐藏,非常方便。
PopupDialog 开发和使用注意事项:
1.
navigatorKey
的使用
确保全局唯一:
navigatorKey
是全局的GlobalKey<NavigatorState>
,必须在应用的顶层(如MaterialApp
)中定义并传递,确保其唯一性。如果在多个地方重复定义或使用不同的
navigatorKey
,可能会导致无法正确获取OverlayState
,从而无法显示或移除对话框。正确传递给
MaterialApp
:
在
MaterialApp
中设置navigatorKey
,例如:dart复制
MaterialApp( navigatorKey: PopupDialog.navigatorKey, ... );
如果没有正确传递,调用
PopupDialog.show
时会打印错误信息,提示OverlayState is null
。2. 对话框的显示和移除
避免重复显示:
在调用
PopupDialog.show
之前,建议检查是否已经有对话框显示。可以通过检查_overlayEntry
是否为null
来判断:dart复制
if (_overlayEntry == null) { PopupDialog.show(...); }
避免重复调用
show
方法,否则可能会导致多个对话框叠加显示。确保移除对话框:
在不再需要对话框时,调用
PopupDialog.remove
方法移除对话框:dart复制
PopupDialog.remove();
如果不移除对话框,可能会导致内存泄漏或其他意外行为。
3. 对话框的样式和布局
自定义样式:
对话框的样式(如背景色、按钮样式、圆角等)可以通过修改
show
方法中的Material
和ElevatedButton
的样式来调整。确保样式符合应用的整体设计风格。
适配不同屏幕:
对话框的布局(如内边距、按钮间距等)需要考虑不同屏幕尺寸的适配。可以使用
MediaQuery
或其他适配工具来动态调整布局。4. 按钮点击事件
合理设置回调:
在调用
PopupDialog.show
时,为按钮设置合理的点击事件回调。例如:dart复制
PopupDialog.show( context, '标题', '按钮1', '按钮2', () { // 按钮1的逻辑 }, () { // 按钮2的逻辑 PopupDialog.remove(); }, );
确保回调逻辑清晰,避免复杂逻辑直接写在回调中,可以提取到单独的方法中。
避免阻塞主线程:
如果按钮的点击事件中包含耗时操作(如网络请求、文件操作等),建议使用
async/await
或其他异步处理方式,避免阻塞主线程导致界面卡顿。5. 错误处理
检查
OverlayState
:
在调用
PopupDialog.show
时,如果OverlayState
为null
,会打印错误信息。确保在调用前检查navigatorKey
是否正确传递给MaterialApp
。处理异常情况:
在对话框的显示和移除逻辑中,添加异常处理机制,避免因意外情况导致应用崩溃。例如:
dart复制
try { PopupDialog.show(...); } catch (e) { print('Error showing dialog: $e'); }
6. 测试
不同场景测试:
测试对话框在不同场景下的表现,包括:
对话框的显示和移除。
按钮点击事件的触发。
在不同屏幕尺寸和设备上的布局适配。
在多语言环境下的显示效果(如果支持国际化)。
性能测试:
确保对话框的显示和移除不会对应用的性能产生负面影响,特别是在高频率调用的情况下。
通过以上注意事项,可以确保
PopupDialog
在开发和使用过程中更加稳定、高效和符合需求。
三、简单案例实现
1、这里使用 Android Studio 进行创建 Flutter 项目
2、创建一个 application 的 Flutter 项目
3、初次的项目结构如下
4、编写实现 PopupDialog 代码
5、在 main 进行功能测试
6、连接设备,运行效果如下
四、关键代码
1、PopupDialog
import 'package:flutter/material.dart';
class PopupDialog {
static OverlayEntry? _overlayEntry; // 用于存储弹出的 OverlayEntry,方便后续移除
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // 全局的 NavigatorState 的 key,用于获取 OverlayState
// 显示弹出对话框的方法
static void show(BuildContext context, String title, String buttonText1, String buttonText2, VoidCallback action1, VoidCallback action2) {
final OverlayState? overlayState = navigatorKey.currentState?.overlay; // 通过 navigatorKey 获取当前的 OverlayState
// 如果 OverlayState 为 null,说明当前没有可用的 Overlay,打印错误信息并返回
if (overlayState == null) {
print("[CustomDialog] show : OverlayState is null. Make sure to use MaterialApp with navigatorKey.");
return;
}
// 创建一个 OverlayEntry,其 builder 方法返回弹出的对话框的 UI
_overlayEntry = OverlayEntry(
builder: (context) {
return Material(
color: Colors.transparent, // 设置 Material 的背景色为透明,让弹出的对话框有半透明的遮罩效果
child: Center(
child: Material(
color: Colors.black.withOpacity(0.8), // 设置弹出的对话框的背景色为半透明的黑色
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0), // 设置对话框的圆角
),
child: Padding(
padding: const EdgeInsets.all(16.0), // 设置对话框的内边距
child: Column(
mainAxisSize: MainAxisSize.min, // 让 Column 的高度尽可能小,以适应内容
children: <Widget>[
Text(
title, // 对话框的标题
style: TextStyle(
color: Colors.white, // 标题文字颜色为白色
fontSize: 16, // 标题文字大小
),
),
SizedBox(height: 10), // 在标题和按钮之间添加间距
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 设置按钮在垂直方向上的布局方式
children: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent, // 设置按钮背景为透明
foregroundColor: Colors.white, // 设置按钮文字颜色为白色
elevation: 0, // 设置按钮无阴影
shadowColor: Colors.transparent, // 设置按钮无阴影颜色
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5), // 设置按钮的圆角
side: BorderSide.none, // 设置按钮无边框
),
),
child: Text(buttonText1), // 按钮1的文字
onPressed: action1, // 按钮1的点击事件
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent, // 设置按钮背景为透明
foregroundColor: Colors.grey, // 设置按钮文字颜色为灰色
elevation: 0, // 设置按钮无阴影
shadowColor: Colors.transparent, // 设置按钮无阴影颜色
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5), // 设置按钮的圆角
side: BorderSide.none, // 设置按钮无边框
),
),
child: Text(buttonText2), // 按钮2的文字
onPressed: action2, // 按钮2的点击事件
),
],
),
],
),
),
),
),
);
},
);
overlayState.insert(_overlayEntry!); // 将创建好的 OverlayEntry 插入到 Overlay 中,从而显示弹出的对话框
}
// 移除弹出的对话框的方法
static void remove() {
if (_overlayEntry != null) {
_overlayEntry!.remove(); // 移除 OverlayEntry
_overlayEntry = null; // 将 _overlayEntry 置为 null,方便后续判断
}
}
}
代码说明:
1. 类的定义与成员变量
_overlayEntry
说明:这是
PopupDialog
类中定义的静态成员变量,用于存储当前显示的OverlayEntry
对象。作用:通过这个对象,可以在需要时移除对话框。
navigatorKey
说明:这是
PopupDialog
类中定义的全局GlobalKey<NavigatorState>
。作用:用于获取当前的
OverlayState
。在MaterialApp
中设置navigatorKey
,确保PopupDialog
可以通过这个navigatorKey
获取到当前的OverlayState
,从而能够正确显示和移除对话框。2. 方法定义
2.1
show
方法
方法定义:
static void show(BuildContext context, String title, String buttonText1, String buttonText2, VoidCallback action1, VoidCallback action2)
作用:
用于显示自定义对话框。
参数说明:
context
:当前的上下文,通常是从调用处传递过来的BuildContext
。
title
:对话框的标题。
buttonText1
和buttonText2
:两个按钮的文本。
action1
和action2
:两个按钮的点击事件回调。2.2
remove
方法
方法定义:
static void remove()
作用:
用于移除当前显示的对话框。
逻辑:
如果
_overlayEntry
不为null
,调用_overlayEntry!.remove()
移除对话框,并将_overlayEntry
置为null
,以便后续可以重新显示对话框。3. 对话框的 UI 结构
主体部分:
使用
Material
,背景色为半透明的黑色(Colors.black.withOpacity(0.8)
),并设置圆角(borderRadius: BorderRadius.circular(10.0)
)。内容组成:
标题:
使用
Text
组件显示标题,文字颜色为白色,字体大小为 16。按钮:
包含两个
ElevatedButton
按钮:
第一个按钮:
文字为
buttonText1
,点击事件为action1
。按钮样式:背景透明,文字颜色为白色,无阴影,圆角为 5。
第二个按钮:
文字为
buttonText2
,点击事件为action2
。按钮样式:背景透明,文字颜色为灰色,无阴影,圆角为 5。
4. 按钮点击事件
第一个按钮:
点击时,执行
action1
回调。第二个按钮:
点击时,执行
action2
回调。5. 使用方式
在主应用中,只需要调用
PopupDialog.show
方法即可显示对话框,调用PopupDialog.remove
方法即可移除对话框。通过这种方式,
PopupDialog
类可以灵活地显示和移除自定义对话框,而主应用只需要调用show
和remove
方法即可控制对话框的显示和隐藏。
2、main
import 'package:flutter/material.dart';
import 'package:test_popup_dialog/popup_dialog.dart'; // 导入自定义的 PopupDialog 类
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 创建一个 MaterialApp
return MaterialApp(
navigatorKey: PopupDialog.navigatorKey, // 将 PopupDialog 的 navigatorKey 传递给 MaterialApp,用于获取 OverlayState
home: Scaffold(
appBar: AppBar(
title: Text('Custom Dialog Example'), // 设置应用栏的标题
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 调用 PopupDialog 的 show 方法显示自定义对话框
PopupDialog.show(
context,
'短信可能有延迟,请再等一会', // 对话框的标题
'再等一会', // 第一个按钮的文字
'返回', // 第二个按钮的文字
() {
// 第一个按钮的点击事件
print('Wait button pressed'); // 打印日志
},
() {
// 第二个按钮的点击事件
PopupDialog.remove(); // 调用 PopupDialog 的 remove 方法移除对话框
print('Return button pressed'); // 打印日志
},
);
},
child: Text('Show Dialog'), // 按钮的文字
),
),
),
);
}
}
代码说明:
PopupDialog.navigatorKey
:
这是
PopupDialog
类中定义的全局navigatorKey
,用于获取当前的OverlayState
。在
MaterialApp
中设置navigatorKey
,确保PopupDialog
可以通过这个navigatorKey
获取到当前的OverlayState
,从而能够正确显示和移除对话框。
PopupDialog.show
方法:
这是
PopupDialog
类中定义的静态方法,用于显示自定义对话框。参数说明:
context
:当前的上下文。
title
:对话框的标题。
buttonText1
和buttonText2
:两个按钮的文字。
action1
和action2
:两个按钮的点击事件回调。
PopupDialog.remove
方法:
这是
PopupDialog
类中定义的静态方法,用于移除当前显示的对话框。按钮点击事件:
第一个按钮点击时,打印日志
"Wait button pressed"
。第二个按钮点击时,调用
PopupDialog.remove
方法移除对话框,并打印日志"Return button pressed"
。通过这种方式,
PopupDialog
类可以灵活地显示和移除自定义对话框,而主应用只需要调用show
和remove
方法即可控制对话框的显示和隐藏。