应用框架与交互组件
本文档介绍 JieLi_Home Demo 应用(code/JieLi_Home_Demo/example)中基于 Flutter 的应用框架与交互组件体系:data/ 目录下的管理器(Manager)如何组织业务能力、dialog/ 目录下的弹窗组件如何实现通用交互,以及两者之间如何通过 showDialog / Navigator 与 jl_home 插件包协同工作。
Purpose and Scope
本页面覆盖以下内容:
- 应用框架层:以
DialogManager为代表的「管理器(Manager)」模式——每个业务域一个 Manager,负责弹窗的构建(build)与展示(show),并通过MethodChannel与原生层通信。 - 交互组件层:
dialog/目录下的通用弹窗组件体系,重点深入分析通用确认弹窗CustomConfirmDialog(标题、消息、单/双按钮、国际化、样式常量)。 - 框架协作机制:
showDialog→Navigator.push→ 用户操作 →Navigator.pop→ 回调(VoidCallback)的完整控制流,以及context.mounted异步安全模式。
以下主题属于兄弟页面,本文仅作交叉引用、不深入展开:
- OTA 升级的具体业务流程(
OtaDialog、ota_file_manager的内部状态机)→ 见 OTA 升级相关页面。 - 语言切换与翻译管理(
select_language_manager、translate_page_manager)→ 见国际化页面。 - 设备配对/连接链路(
face_to_face_manager、MTU 调整等)→ 见设备连接页面。 - 设置项持久化(
setting_manager)→ 见设置页面。
Overview
这是一个 Flutter 插件示例工程(example),宿主业务全部围绕蓝牙设备(杰理科技 JL 系列)的发现、连接、OTA 升级与通话翻译展开。由于业务天然是「任务 + 确认 + 结果反馈」的交互模型,弹窗(Dialog)成为最主要的交互载体,由此沉淀出一套统一的应用框架约定:
- 管理器与 UI 分离:业务逻辑收敛在
data/*_manager.dart,弹窗组件收敛在dialog/*.dart。页面只负责调用 Manager,Manager 负责组装弹窗并决定何时展示。 - 构建与展示分离:Manager 提供
buildXxxDialog()(返回 Widget)与showXxxDialog()(内部调用showDialog并await结果),便于单元测试与复用。 - 回调而非返回值:弹窗组件通过
VoidCallback?(onConfirm/onCancel)通知业务层用户的选择,而不是自己处理业务,从而保持组件无业务依赖、可任意复用。 - 统一视觉规范:弹窗圆角 12、确认主色
0xFF398BFF、取消色0xFFB0B0B0、正文色0xFF242424、分割线0xFFF5F5F5,按钮高度取自插件包常量AppConstants.dialogButtonHeight。 - 国际化内置:所有文案默认值来自
AppLocalizations(l10n/app_localizations.dart),组件本身不硬编码字符串。 - 原生桥接:需要读写文件等原生能力的弹窗(如
SelectFileSaveDialog)通过MethodChannel与宿主原生侧通信。
Architecture
下图展示了「页面 → 管理器 → 弹窗组件 → 插件包/框架」的分层关系(节点均取自真实源码):
flowchart TD
subgraph sg_App["Demo 应用 (example/lib)"]
subgraph sg_Manager["管理器层 (data/)"]
DialogManager["DialogManager"]
end
subgraph sg_Dialog["交互组件层 (dialog/)"]
CustomConfirmDialog["CustomConfirmDialog"]
OtaDialog["OtaDialog"]
SelectFileSaveDialog["SelectFileSaveDialog"]
end
end
subgraph sg_Plugin["jl_home 插件包"]
AppConstants["AppConstants (constants.dart)"]
AppLocalizations["AppLocalizations (l10n)"]
end
subgraph sg_Framework["Flutter Framework"]
ShowDialog["showDialog()"]
NavigatorPop["Navigator.pop()"]
end
DialogManager -->|"buildOtaDialog() 构建"| OtaDialog
DialogManager -->|"buildSaveFileDialog() 构建"| SelectFileSaveDialog
DialogManager -->|"注入 MethodChannel"| SelectFileSaveDialog
DialogManager -->|"await showDialog()"| ShowDialog
CustomConfirmDialog --> AppConstants
CustomConfirmDialog --> AppLocalizations
SelectFileSaveDialog --> AppLocalizations
OtaDialog --> AppLocalizations
CustomConfirmDialog --> NavigatorPop
OtaDialog --> NavigatorPop
分层职责说明:
- 管理器层(data/):
DialogManager持有页面BuildContext与MethodChannel两个依赖(见 dialog_manager.dart#L8-L12)。它的职责是"知道什么时候弹什么窗",而不是"弹窗长什么样"。 - 交互组件层(dialog/):纯展示组件,只接收数据与回调。
CustomConfirmDialog不依赖任何 Manager,仅依赖插件包常量与本地化资源,这是它能在全应用复用的根本原因。 - 插件包(jl_home):
AppConstants.dialogButtonHeight提供统一尺寸常量;AppLocalizations提供多语言文案。两者都是静态/单例式访问,组件通过import 'package:jl_home/...'与相对路径../l10n/app_localizations.dart引入。 - Flutter 框架:
showDialog负责把弹窗 Widget 推入路由栈,Navigator.pop负责关闭;showDialog返回的Future与context.mounted检查共同构成异步安全闭环。
设计意图:分层的关键在于依赖方向单向——页面依赖 Manager,Manager 依赖弹窗组件,组件只依赖框架与常量。这样新增一个弹窗只需"加一个组件 + 在 Manager 加一个方法",不触碰其他模块;而统一文案与样式常量则保证全应用弹窗视觉与语言的一致性。
主内容:管理器模式(应用框架核心)
DialogManager:构建与展示分离
DialogManager 是应用框架的样板实现,完整源码仅 31 行,却定义了整个 Demo 的弹窗组织范式(dialog_manager.dart):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../dialog/ota_dialog.dart';
import '../dialog/select_file_save_dialog.dart';
/// Manages the display of various dialogs related to OTA operations.
class DialogManager {
final BuildContext context;
final MethodChannel methodChannel;
DialogManager({required this.context, required this.methodChannel});
Widget buildSaveFileDialog(String fileName) {
return SelectFileSaveDialog(fileName: fileName, methodChannel: methodChannel);
}
Widget buildOtaDialog() {
return OtaDialog();
}
Future<void> showOtaDialog() async {
final dialog = buildOtaDialog();
await showDialog(context: context, builder: (context) => dialog);
}
Future<void> showSaveFileDialog(String fileName) async {
final dialog = buildSaveFileDialog(fileName);
await showDialog(context: context, builder: (context) => dialog);
}
}
Source: dialog_manager.dart
逐段剖析其设计意图:
- 构造注入依赖(L9-L12):
context与methodChannel通过构造函数注入而非全局单例。这使得 Manager 可以被测试替身替换,也避免了context跨页面泄漏——每个页面实例化自己的 Manager 时传入自己作用域内的context。 buildXxxDialog()返回 Widget(L14-L20):构建方法不执行展示,只负责组装组件。buildSaveFileDialog展示了依赖传递的典型写法——把 Manager 持有的methodChannel注入到需要原生能力的弹窗中。showXxxDialog()负责展示(L22-L30):展示方法内部调用 Flutter 的showDialog,并把builder直接返回已构建好的组件实例。await关键字说明调用方可等待弹窗关闭后继续执行——这是"弹窗即 Future"的异步交互模型。- 领域聚合(L4-L5、L18-L19):Manager 的命名与注释(
OTA operations)表明它聚合了一个业务域内的全部弹窗,而不是每个弹窗一个 Manager。后续新增 OTA 相关弹窗(如 MTU 调整、进度展示)都应挂载到这个 Manager 上。
Manager 模式家族
data/ 目录下与 DialogManager 并列的其他管理器(本页未逐一读取其实现,职责依据文件命名与目录结构推断,见各文件):
| 文件 | 推断职责 | 相关弹窗/页面 |
|---|---|---|
dialog_manager.dart | OTA 弹窗聚合管理(已读取) | OtaDialog、SelectFileSaveDialog |
ota_file_manager.dart | OTA 固件文件的选择与校验 | select_file_save_dialog.dart |
face_to_face_manager.dart | 面对面(点对点)连接流程 | device_password_dialog.dart 等 |
popup_menu_manager.dart | 页面 PopupMenu 菜单项的组织 | 页面菜单 |
select_language_manager.dart | 语言选择逻辑 | select_language 相关页面 |
setting_manager.dart | 设置项读写与状态 | 设置页面 |
translate_page_manager.dart | 通话翻译页业务 | call_translation_tips_dialog.dart |
说明:上述推断基于文件命名与 Demo 业务常识;除
DialogManager外其余 Manager 的内部实现细节未在本页读取,请以对应源码为准。
主内容:通用确认弹窗 CustomConfirmDialog
CustomConfirmDialog 是交互组件层的基石组件——一个零业务依赖、可配置标题/消息/按钮的通用确认弹窗(custom_confirm_dialog.dart)。
可配置接口
class CustomConfirmDialog extends StatelessWidget {
final VoidCallback? onCancel;
final VoidCallback? onConfirm;
final String? title;
final String? message;
final String? cancelText;
final String? confirmText;
final bool showTitle;
final bool showSingleButton;
static const Color darkTextColor = Color(0xFF242424);
static const Color confirmTextColor = Color(0xFF398BFF);
static const Color cancelTextColor = Color(0xFFB0B0B0);
static const Color dialogDividerColor = Color(0xFFF5F5F5);
const CustomConfirmDialog({
super.key,
this.onCancel,
this.onConfirm,
this.title,
this.message,
this.cancelText,
this.confirmText,
this.showTitle = true,
this.showSingleButton = false,
});
...
}
Source: custom_confirm_dialog.dart
设计要点:
- 全部参数可空 + 默认值兜底:
title/message/按钮文案均可省略。组件在build中通过AppLocalizations计算有效值(effectiveMessage = message ?? loc.saveAndRestartMessage,effectiveConfirmText = confirmText ?? loc.restart),因此调用方只需传onConfirm一个参数即可得到一个"重启确认框"。兜底文案来自本地化资源而非硬编码,这是国际化的正确姿势。 StatelessWidget+const构造:组件无内部状态,所有展示信息由构造参数决定,可安全地const实例化,避免无谓重建。- 双开关控制形态:
showTitle控制是否显示标题;showSingleButton控制是单按钮(仅确认)还是双按钮(取消 + 确认)形态。
视觉与布局实现
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (showTitle && effectiveTitle.isNotEmpty) _buildTitle(effectiveTitle),
_buildMessage(effectiveMessage),
const Divider(height: 1, color: dialogDividerColor),
_buildButtons(
context,
effectiveCancelText,
effectiveConfirmText,
cancelTextColor,
confirmTextColor,
),
],
),
),
);
Source: custom_confirm_dialog.dart
结构自上而下为:可选标题 → 消息 → 1px 分割线 → 按钮区。关键实现细节:
- 外层
Dialog提供系统级弹窗语义(遮罩、圆角裁剪、居中布局),Material保证水波纹等 Material 组件可用,Column(mainAxisSize: MainAxisSize.min)让弹窗高度收缩到内容。 - 标题字号 18、加粗;消息字号 15、常规;两者统一使用
darkTextColor(0xFF242424),居中显示。 - 消息顶部间距是动态的(
showTitle && title 非空 ? 12 : 32):有标题时消息贴近标题,无标题时消息上移补偿,保证视觉重心不偏。 - 分割线用
Divider(height: 1)压成 1px 细线,颜色0xFFF5F5F5与按钮区分隔。
按钮区:单/双按钮与安全回调
Widget _buildButtons(BuildContext context, String cancelText, String confirmText,
Color cancelColor, Color confirmColor) {
if (showSingleButton) {
return _buildSingleButton(context, confirmText, confirmColor);
}
return _buildDoubleButtons(context, cancelText, confirmText, cancelColor, confirmColor);
}
Source: custom_confirm_dialog.dart
双按钮模式采用 Row + 两个 Expanded 各占一半宽度、中间夹 1px 竖分割线的布局(L121-L179)。每个按钮都是 InkWell(关闭水波纹/高亮、透明 splashColor/highlightColor),高度统一为 AppConstants.dialogButtonHeight。取消按钮文字色 0xFFB0B0B0 常规字重,确认按钮文字色 0xFF398BFF 加粗——用字重与颜色双重编码主次操作,符合移动端确认框惯例。
点击处理是异步安全的样板(以确认按钮为例):
Expanded(
child: InkWell(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onTap: () {
if (context.mounted) {
Navigator.pop(context);
onConfirm?.call();
}
},
...
),
),
Source: custom_confirm_dialog.dart
context.mounted 检查是 Flutter 3.7+ 的标准防御:在异步回调或延迟场景中,Widget 可能已从树中移除,此时调用 Navigator.pop(context) 会抛异常。先关窗(Navigator.pop)再回调(onConfirm?.call())的顺序保证:回调执行时弹窗必然已关闭,业务层可以直接继续下一步,不会被仍在栈顶的弹窗遮挡。
交互组件族谱
dialog/ 目录共 13 个弹窗组件,均遵循上述"无业务、可配置、回调通知"的范式。除 CustomConfirmDialog 外,其余组件的内部实现本页未逐一读取,用途依据文件命名与 DialogManager 中的引用关系整理:
| 组件文件 | 用途(依据命名/引用推断) | 关联管理器 |
|---|---|---|
custom_confirm_dialog.dart | 通用确认弹窗(已读取) | 各业务页面 |
loading_dialog.dart | 通用加载中转圈 | 各业务页面 |
loading_content_dialog.dart | 带内容/进度描述的加载弹窗 | OTA 等长任务 |
custom_loading_dialog.dart | 定制样式加载弹窗 | OTA 等长任务 |
ota_dialog.dart | OTA 升级主弹窗(已确认被 DialogManager 引用) | DialogManager |
select_file_save_dialog.dart | 选择固件保存路径(已确认接收 MethodChannel) | DialogManager、OtaFileManager |
mtu_adjustment_dialog.dart | MTU 参数调整 | 设备连接 |
device_filter_dialog.dart | 设备过滤条件选择 | 设备列表 |
device_password_dialog.dart | 设备密码输入 | FaceToFaceManager |
multi_links_dialog.dart | 多设备同时连接管理 | 设备连接 |
rename_dialog.dart | 设备重命名 | 设备详情 |
call_translation_tips_dialog.dart | 通话翻译提示 | TranslatePageManager |
诚实性说明:表中除已标注"已读取/已确认"的条目外,均为依据文件命名与 Demo 业务域的合理推断;若需精确行为请直接阅读对应源文件。
核心流程:弹窗展示与回调闭环
展示时序(Manager 驱动)
一次典型的弹窗交互(以 showOtaDialog 为例)在组件间的时间顺序如下:
sequenceDiagram
participant Page as 业务页面
participant DM as DialogManager
participant FD as showDialog (Flutter)
participant Dlg as OtaDialog / 弹窗组件
participant Nav as Navigator
Page->>DM: showOtaDialog()
DM->>DM: buildOtaDialog() 构建组件实例
DM->>FD: showDialog(context: context, builder: (c) => dialog)
FD->>Nav: push 路由 (弹窗入栈)
Nav-->>Page: 返回 Future (await 挂起)
Page-->>Dlg: 弹窗渲染,等待用户操作
Dlg->>Dlg: 用户点击确认/取消按钮
Dlg->>Nav: Navigator.pop(context)
Nav-->>FD: 路由出栈
FD-->>Page: Future 完成,await 恢复
Page->>Page: 继续后续业务逻辑
关键点:await 让页面在弹窗展示期间挂起,弹窗关闭后从 showDialog 处继续执行。因此弹窗交互本质上是"Future 化"的——业务代码可以像写同步逻辑一样组织"先确认、再执行"的流程,无需监听器或事件总线。
弹窗内部决策流(CustomConfirmDialog)
flowchart TD
Start(["build(context)"]) --> L10n["获取 AppLocalizations 兜底文案"]
L10n --> TitleCheck{"showTitle 且标题非空?"}
TitleCheck -->|"是"| BuildTitle["渲染标题 (18px 加粗)"]
TitleCheck -->|"否"| SkipTitle["跳过标题"]
BuildTitle --> Message["渲染消息 (15px)"]
SkipTitle --> Message
Message --> Divider["渲染 1px 分割线"]
Divider --> ModeCheck{"showSingleButton?"}
ModeCheck -->|"是"| Single["单按钮 (仅确认)"]
ModeCheck -->|"否"| Double["双按钮 (取消+确认)"]
Single --> Tap{"用户点击按钮"}
Double --> Tap
Tap -->|"点击"| Mounted{"context.mounted?"}
Mounted -->|"否"| Skip["跳过回调 (防崩溃)"]
Mounted -->|"是"| Pop["Navigator.pop(context)"]
Pop --> Callback["调用 onConfirm / onCancel 回调"]
Callback --> End(["结束"])
Skip --> End
决策顺序的设计逻辑:组件先做文案兜底(保证任何参数组合都能渲染出合理内容),再按开关决定布局形态,最后在交互出口统一做 context.mounted 防御。所有分支都收敛到"弹窗关闭 + 回调通知"两个动作,业务侧无需关心组件内部形态。
使用示例
示例一:通过 DialogManager 展示 OTA 弹窗
页面持有 Manager 实例,直接调用 showXxxDialog 即可;展示与关闭的细节全部封装在 Manager 内:
final DialogManager _dialogManager = DialogManager(
context: context,
methodChannel: methodChannel,
);
// 用户点击"OTA 升级"后
await _dialogManager.showOtaDialog();
// 用户点击"选择保存文件"后
await _dialogManager.showSaveFileDialog('firmware_v1.2.3.bin');
Source: dialog_manager.dart
示例二:直接使用 CustomConfirmDialog(默认双按钮确认)
业务页面里最常见的用法——只传必要参数,其余走本地化默认值(默认场景为"重启确认"):
await showDialog(
context: context,
builder: (context) => CustomConfirmDialog(
onConfirm: () => _restartApp(),
),
);
Source: custom_confirm_dialog.dart
示例三:完全自定义的确认框(标题 + 消息 + 单按钮)
当需要"仅提示、仅一个确认动作"的场景(如操作成功提示),关闭标题并切换单按钮模式:
showDialog(
context: context,
builder: (context) => CustomConfirmDialog(
showTitle: false,
showSingleButton: true,
message: loc.operationSucceeded,
confirmText: loc.gotIt,
onConfirm: () => Navigator.of(context).pop(),
),
);
Source: custom_confirm_dialog.dart
说明:示例三中的
loc.operationSucceeded/loc.gotIt为示意性本地化键(AppLocalizations实际键名以 app_localizations.dart 为准),重点在于演示showTitle/showSingleButton/confirmText三个开关的组合用法。
配置选项
CustomConfirmDialog 构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
onCancel | VoidCallback? | null | 点击取消按钮(或单按钮模式外)时回调;为 null 时仅关闭弹窗 |
onConfirm | VoidCallback? | null | 点击确认按钮时回调;为 null 时仅关闭弹窗 |
title | String? | null(空字符串) | 弹窗标题;配合 showTitle 使用 |
message | String? | loc.saveAndRestartMessage | 弹窗正文;缺省时使用本地化的"保存并重启"文案 |
cancelText | String? | loc.cancel | 取消按钮文案;缺省时使用本地化"取消" |
confirmText | String? | loc.restart | 确认按钮文案;缺省时使用本地化"重启" |
showTitle | bool | true | 是否渲染标题区;为 false 时消息顶部 padding 增大以补偿视觉重心 |
showSingleButton | bool | false | true 时只渲染单个确认按钮;false 时渲染"取消 + 确认"双按钮 |
视觉常量(组件内定义,不可外部覆盖)
| 常量 | 值 | 用途 |
|---|---|---|
darkTextColor | Color(0xFF242424) | 标题与消息文字颜色 |
confirmTextColor | Color(0xFF398BFF) | 确认按钮文字颜色(主操作) |
cancelTextColor | Color(0xFFB0B0B0) | 取消按钮文字颜色(次操作) |
dialogDividerColor | Color(0xFFF5F5F5) | 消息/按钮间分割线颜色 |
AppConstants.dialogButtonHeight | 插件包常量 | 按钮区高度(来自 jl_home 插件包 constants.dart) |
DialogManager 构造依赖
| 参数 | 类型 | 说明 |
|---|---|---|
context | BuildContext | 调用方页面上下文,用于 showDialog |
methodChannel | MethodChannel | 原生通道,透传给需要原生能力的弹窗(如 SelectFileSaveDialog) |
API 参考
DialogManager
DialogManager({required this.context, required this.methodChannel})
说明:OTA 相关弹窗的管理器,采用构造注入依赖、构建与展示分离的设计。
Widget buildSaveFileDialog(String fileName)
- 参数:
fileName(String)——待保存的固件文件名。 - 返回:
SelectFileSaveDialog组件实例(内部注入methodChannel)。 - 抛错:无(纯构建)。
Widget buildOtaDialog()
- 参数:无。
- 返回:
OtaDialog组件实例。 - 抛错:无(纯构建)。
Future<void> showOtaDialog()
- 参数:无。
- 返回:
Future<void>,弹窗关闭时完成。 - 行为:构建
OtaDialog并await showDialog(context: context, ...)展示。
Future<void> showSaveFileDialog(String fileName)
- 参数:
fileName(String)——待保存的固件文件名。 - 返回:
Future<void>,弹窗关闭时完成。 - 行为:构建
SelectFileSaveDialog并展示。
CustomConfirmDialog
const CustomConfirmDialog({
super.key,
this.onCancel,
this.onConfirm,
this.title,
this.message,
this.cancelText,
this.confirmText,
this.showTitle = true,
this.showSingleButton = false,
})
说明:通用确认弹窗(StatelessWidget),通过回调而非返回值与业务层通信。
参数:见上文"配置选项"表。
返回/抛错:无返回值、不抛异常;回调可空、context.mounted 防御保证点击处理不会因 Widget 已销毁而崩溃。
失败模式、边界情况与并发
1. 异步回调中的 context 失效
弹窗按钮回调可能发生在 Widget 已从树中移除之后(例如页面在弹窗展示期间被路由替换)。若此时直接 Navigator.pop(context) 会抛出 FlutterError。组件统一用 if (context.mounted) 包裹(见 custom_confirm_dialog.dart#L158-L163):已卸载则静默跳过,避免崩溃。
2. 可空回调与文案兜底
所有回调与文案参数均可为空。回调为空时弹窗"只关窗不做事";文案为空时使用 AppLocalizations 本地化默认值。代价是语义耦合:默认文案围绕"保存并重启"场景设计,若业务需其他语义必须显式传 message/confirmText,否则会出现与预期不符的文案——这是该组件当前已知的边界约束。
3. 弹窗并发与路由栈
Flutter 的 showDialog 基于 Navigator.push,多个弹窗连续弹出会依次入栈,后弹出的盖住先弹出的。由于所有 showXxxDialog 都返回 Future,页面可用 await 串行化弹窗流程;若并发调用(未 await)则表现为栈式叠加,关闭顺序为后进先出。Manager 层不提供互斥队列,需要串行语义时由页面自行 await——这是框架的有意取舍(简单、可预测)。
4. 单按钮模式的取消语义
showSingleButton: true 时组件不渲染取消按钮,但用户仍可点击遮罩或系统返回键关闭弹窗(Dialog 默认 barrierDismissible 行为未显式关闭),此时 onCancel 不会被调用(单按钮分支只接 onConfirm)。需要拦截遮罩关闭时,应在调用处改用 showDialog(barrierDismissible: false, ...) 包裹。
5. MethodChannel 的线程与平台边界
SelectFileSaveDialog 通过注入的 MethodChannel 发起原生调用。Demo 中该通道由宿主原生侧实现;若原生侧未注册对应 handler,调用会抛出 MissingPluginException。Manager 层不捕获此异常,由业务页面在 await showXxxDialog() 之后统一处理(或依赖全局错误上报)。
性能与运维注意事项
- 组件均为无状态轻量 Widget:
CustomConfirmDialog是StatelessWidget且支持const构造,重建成本极低;弹窗内容为单个Column+ 少量Text/InkWell,无图片、无动画,对低端设备友好。 - 无网络/无轮询:弹窗体系不引入额外网络请求;长任务进度类弹窗(
loading_content_dialog、ota_dialog)由业务层驱动刷新,组件本身不持有定时器,避免后台泄漏。 showDialog的挂起成本:await showDialog(...)期间页面 BuildContext 保持活跃。若弹窗长时间不关闭(如等待用户选择文件),页面处于"逻辑挂起"状态——这是设计预期,但应避免在弹窗展示期间发起与结果无关的重型计算。- 导航栈深度:连续弹出多个弹窗会叠加路由栈深度;Demo 场景(确认 → 选择文件 → 进度)最多约 3 层,Flutter 无实际问题。若未来出现更深嵌套,建议改用全屏页面而非弹窗。
扩展点
新增业务弹窗的标准流程:
- 在
dialog/下新建组件(继承StatelessWidget,参数可空 + 本地化兜底 +VoidCallback回调); - 在对应 Manager(如
DialogManager)中增加buildXxxDialog()与showXxxDialog()两个方法; - 页面通过 Manager 展示,业务逻辑放在回调中。 按此流程新增的弹窗会自动获得:统一视觉、国际化、异步安全与可测试性。
- 在
复用
CustomConfirmDialog的形态开关:showTitle/showSingleButton/文案参数组合可覆盖"确认、提示、成功反馈"三类常见场景;若需要第三按钮或自定义内容区,应新建组件而不是给CustomConfirmDialog堆参数(保持其单一职责)。替换视觉常量:
dialogButtonHeight来自插件包AppConstants,是全局统一尺寸入口;调整一处即可全局生效。组件内的四个static const Color为局部常量,如需要主题化改造,可将其提升为构造参数或主题扩展。Manager 依赖注入:
DialogManager通过构造注入context与methodChannel,为单元测试提供了替身(mock)接口——测试时可用测试BuildContext与 mock 通道验证弹窗构建逻辑。
测试
仓库根级集成测试入口为 plugin_integration_test.dart(integration_test 目录),本页未读取其断言细节。基于源码结构可确认的测试关注点:
- 组件级:
CustomConfirmDialog纯函数式构造(参数 → 布局形态),易于用 widget test 断言标题/消息/按钮数量与回调触发。 - 管理器级:
DialogManager的 build/show 分离设计使测试可以仅验证buildXxxDialog返回的组件类型与参数透传,而无需真实展示弹窗。 - 集成级:Demo 以蓝牙设备交互为主,端到端弹窗流程依赖真实设备或 mock 通道,适合 integration_test 覆盖。
测试细节未在本页展开,具体断言请阅读对应测试文件。
相关链接
- 源码入口:
- dialog_manager.dart — 管理器模式样板
- custom_confirm_dialog.dart — 通用确认弹窗
- app_localizations.dart — 本地化资源
- constants.dart — 插件包常量(
AppConstants)
- 相关目录:
example/lib/dialog/— 全部 13 个弹窗组件example/lib/data/— 全部 7 个业务管理器
- 交叉引用:OTA 升级流程、设备连接与 MTU 调整、语言与翻译管理、设置持久化等主题请参见对应 Wiki 页面。