Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能

news/2025/3/13 20:20:22/

flutter%20%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6%EF%BC%8C%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BC%B9%E7%AA%97PopupDialog%E5%8A%9F%E8%83%BD" name="Flutter%20%E5%AD%A6%E4%B9%A0%E4%B9%8B%E6%97%85%20%E4%B9%8B%20flutter%20%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6%EF%BC%8C%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BC%B9%E7%AA%97PopupDialog%E5%8A%9F%E8%83%BD">Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能

目录

flutter%20%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6%EF%BC%8C%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BC%B9%E7%AA%97PopupDialog%E5%8A%9F%E8%83%BD-toc" name="tableOfContents" style="margin-left:0px">Flutter 学习之旅 之 flutter 不使用插件,实现简单自定义弹窗PopupDialog功能

一、简单介绍

PopupDialog-toc" name="tableOfContents" style="margin-left:40px">二、PopupDialog

三、简单案例实现

四、关键代码


一、简单介绍

Flutter 是一款开源的 UI 软件开发工具包,由 Google 开发和维护。它允许开发者使用一套代码同时构建跨平台的应用程序,包括移动设备(iOS 和 Android)、Web 和桌面平台(Windows、macOS 和 Linux)。

Flutter 使用 Dart 编程语言,它可以将代码编译为 ARM 或 Intel 机器代码以及 JavaScript,从而实现快速的性能。Flutter 提供了一个丰富的预置小部件库,开发者可以根据自己的需求灵活地控制每个像素,从而创建自定义的、适应性强的设计,这些设计在任何屏幕上都能呈现出色的外观和感觉。

PopupDialog" name="%E4%BA%8C%E3%80%81PopupDialog">二、PopupDialog

PopupDialog 是一个自定义的 Flutter 弹出对话框工具类。它通过 Overlay 实现,可以灵活地在应用中显示和移除对话框。类中包含一个全局的 navigatorKey,用于获取当前的 OverlayState,从而确保对话框能够正确显示。show 方法用于显示对话框,支持自定义标题、按钮文本和点击事件回调。remove 方法用于移除当前显示的对话框。对话框的 UI 包括一个半透明的遮罩和一个居中的对话框主体,主体内包含标题和两个按钮。使用时,只需调用 showremove 方法即可控制对话框的显示和隐藏,非常方便。

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 方法中的 MaterialElevatedButton 的样式来调整。

    • 确保样式符合应用的整体设计风格。

  • 适配不同屏幕

    • 对话框的布局(如内边距、按钮间距等)需要考虑不同屏幕尺寸的适配。可以使用 MediaQuery 或其他适配工具来动态调整布局。

4. 按钮点击事件

  • 合理设置回调

    • 在调用 PopupDialog.show 时,为按钮设置合理的点击事件回调。例如:

      dart复制

      PopupDialog.show(context,'标题','按钮1','按钮2',() {// 按钮1的逻辑},() {// 按钮2的逻辑PopupDialog.remove();},
      );
    • 确保回调逻辑清晰,避免复杂逻辑直接写在回调中,可以提取到单独的方法中。

  • 避免阻塞主线程

    • 如果按钮的点击事件中包含耗时操作(如网络请求、文件操作等),建议使用 async/await 或其他异步处理方式,避免阻塞主线程导致界面卡顿。

5. 错误处理

  • 检查 OverlayState

    • 在调用 PopupDialog.show 时,如果 OverlayStatenull,会打印错误信息。确保在调用前检查 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:对话框的标题。

    • buttonText1buttonText2:两个按钮的文本。

    • action1action2:两个按钮的点击事件回调。

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 类可以灵活地显示和移除自定义对话框,而主应用只需要调用 showremove 方法即可控制对话框的显示和隐藏。

2、main

import 'package:flutter/material.dart';
import 'package:test_popup_dialog/popup_dialog.dart'; // 导入自定义的 PopupDialog 类void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {// 创建一个 MaterialAppreturn MaterialApp(navigatorKey: PopupDialog.navigatorKey, // 将 PopupDialog 的 navigatorKey 传递给 MaterialApp,用于获取 OverlayStatehome: 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'), // 按钮的文字),),),);}
}

代码说明:

  1. PopupDialog.navigatorKey

    • 这是 PopupDialog 类中定义的全局 navigatorKey,用于获取当前的 OverlayState

    • MaterialApp 中设置 navigatorKey,确保 PopupDialog 可以通过这个 navigatorKey 获取到当前的 OverlayState,从而能够正确显示和移除对话框。

  2. PopupDialog.show 方法

    • 这是 PopupDialog 类中定义的静态方法,用于显示自定义对话框。

    • 参数说明:

      • context:当前的上下文。

      • title:对话框的标题。

      • buttonText1buttonText2:两个按钮的文字。

      • action1action2:两个按钮的点击事件回调。

  3. PopupDialog.remove 方法

    • 这是 PopupDialog 类中定义的静态方法,用于移除当前显示的对话框。

  4. 按钮点击事件

    • 第一个按钮点击时,打印日志 "Wait button pressed"

    • 第二个按钮点击时,调用 PopupDialog.remove 方法移除对话框,并打印日志 "Return button pressed"

通过这种方式,PopupDialog 类可以灵活地显示和移除自定义对话框,而主应用只需要调用 showremove 方法即可控制对话框的显示和隐藏。


http://www.ppmy.cn/news/1578874.html

相关文章

网络安全linux命令

文章目录&#xff1a; 一&#xff1a;网络信息安全简介 二&#xff1a;网络中存在的威胁 三&#xff1a;常见的攻击类型 1.端口扫描 2.DoS和DDoS攻击 3.特洛依木马&#xff08;Trojan&#xff09; 3.1 木马的工作原理 3.2 特洛伊木马具有的特性 3.3 木马的种类 3.4 被…

JVM垃圾收集器合集

前言&#xff1a;JVM GC收集器的回顾与比较 JVM&#xff08;Java虚拟机&#xff09;中的垃圾收集器是自动管理内存的重要机制&#xff0c;旨在回收不再使用的对象所占用的内存空间。以下是JVM中几种常见的垃圾收集器的详细介绍&#xff1a; 一、新生代垃圾收集器 1.Serial收集…

Vue3中Vue.prototype.$project = base.getProjectName()的语法如何使用?

在 Vue 3 中&#xff0c;Vue.prototype 已被移除&#xff0c;不能再直接向 Vue 的原型上添加全局属性或方法。取而代之的方式是使用 app.config.globalProperties 进行全局属性的挂载。 替代方式 如果你想在 Vue 3 中实现类似 Vue.prototype.$project base.getProjectName() …

刷leetcode hot100--动态规划3.12

第一题乘积max子数组[1h] emmmm感觉看不懂题解 线性dp【计划学一下acwing&#xff0c;挨个做一下】 线性动态规划 相似题解析 最长上升子序列 最大上升子序列和 最大连续子段和 乘积最大子数组_哔哩哔哩_bilibili 比较奇怪的就是有正负数和0&#xff0c;如何处理&#xff1f…

更新vscode ,将c++11更新到c++20

要在CentOS系统中安装最新版本的GCC&#xff0c;你可以使用SCL&#xff08;Software Collections&#xff09;仓库&#xff0c;它提供了开发工具的最新版本。以下是安装步骤&#xff1a; 1、 添加SCL仓库&#xff1a; 首先&#xff0c;添加CentOS的SCL仓库&#xff0c;该仓库…

K8S学习之基础二十:k8s的coredns

K8S的coredns DNS&#xff08;DOMAIN NAME SYSTEM&#xff09;&#xff0c;就是域名系统COREDNS是DNS的一种&#xff0c;为集群提供服务发现功能&#xff0c;用来解析集群中生成的服务这里使用dig镜像来测试域名服务dig是继承了nslookup命令的一个镜像&#xff0c;上传到harbor…

STM32 单片机常见的 8 种输入输出模式

STM32 单片机常见的 8 种输入输出模式 文章目录 STM32 单片机常见的 8 种输入输出模式1. 浮空输入模式 (GPIO_Mode_IN_FLOATING)介绍使用场景示例代码 2. 上拉输入模式 (GPIO_Mode_IPU)介绍使用场景示例代码 3. 下拉输入模式 (GPIO_Mode_IPD)介绍使用场景示例代码 4. 模拟输入模…

前端 | 向后端传数据,判断问题所在的调试过程

目录 ​编辑 1. 在 vue 文件中&#xff0c;在调用函数之前 先打印传入的数据 2. 在 js 文件中&#xff0c;打印接收到的数据 3. 在浏览器 Network 面板查看请求数据 4. 在 server.js 中查看请求数据 5. 确保 JSON 格式正确 知识点&#xff1a;JSON.stringify(req.body, …