在移动端 UI 设计中,弹窗(Dialog) 是承载「打断式沟通」(interrupt communication) 的核心控件:它能在适当时机抓住用户注意力,提示风险、请求确认或引导后续操作。下面我沿着 「系统 → 半自定义 → 全自定义」 的脉络,简单说明在Flutter里面写出可扩展的弹窗。
本文完整Demo代码: github.com/wutao23yzd/... 中的Demo6
效果如下所示:
1 系统级弹窗:一行代码就能用
1.1 AlertDialog
------最常见的模态对话框
dart
Future<void> _showDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: true,
builder: (context) => AlertDialog(
title: const Text('弹窗标题'),
content: const Text('这是一个弹窗内容。'),
actions: [
TextButton(child: const Text('取消'), onPressed: () => Navigator.pop(context)),
TextButton(child: const Text('确定'), onPressed: () => Navigator.pop(context)),
],
),
);
}

showDialog
是 Flutter 内置的异步 API;Future
在对话框关闭后完成。barrierDismissible: true
允许点击遮罩关闭,用户体验更友好。AlertDialog
自带标题、正文、按钮插槽,适合提示 + 二次确认的场景。
1.2 SimpleDialog
------最简单的选项列表
dart
Future<void> _changeLanguage() async {
int? result = await showDialog<int>(
context: context,
builder: (context) => SimpleDialog(
title: const Text('选择语言'),
children: [
SimpleDialogOption(child: const Text('中文'), onPressed: () => Navigator.pop(context, 1)),
SimpleDialogOption(child: const Text('English'), onPressed: () => Navigator.pop(context, 2)),
],
),
);
}

SimpleDialog
省去了布局细节,只应对轻量级多选一 ;返回值 result
让你能在调用处直接拿到用户选择。
2 系统 BottomSheet:从屏幕底部滑入
dart
Future<void> _showBottomSheet() async {
return showModalBottomSheet<void>(
context: context,
builder: (context) => Wrap(
children: [
ListTile(title: const Center(child: Text('选项 1')), onTap: () => Navigator.pop(context)),
ListTile(title: const Center(child: Text('选项 2')), onTap: () => Navigator.pop(context)),
const Divider(),
ListTile(
title: const Center(child: Text('取消', style: TextStyle(color: Colors.red))),
onTap: () => Navigator.pop(context),
),
],
),
);
}

showModalBottomSheet
默认带遮罩、支持手势下滑关闭;用Wrap
自适应高度。- 典型场景:iOS 风格 ActionSheet、文件操作菜单等。
3 迈向完全自定义
系统组件虽方便,但在品牌一致性、复杂交互、动画细节上往往力不从心。在Demo的自定义方案中,拆成 数据层 + 动效层 + API 层。
3.1 数据模型:ModalOption
dart
class ModalOption {
final String? name; // 选项文案
final Widget? icon; // 自定义图标
final IconData? iconData; // 或者用系统 Icon
final Widget? child; // 复杂场景直接塞子组件
final VoidCallback? _onTap; // 点击回调
final bool distractive; // 危险操作标识(高亮红色)
...
}
- 职责单一 :只描述「菜单项」的长相 与行为。
copyWith
保留 immutable 风格,后续修改也安全。distractiveColor
把「危险高亮」逻辑封装内部,外部不用再判红色。
3.2 动效控件:Tappable
dart
class Tappable extends StatefulWidget {
const Tappable.faded({ required this.child, this.onTap, FadeStrength fadeStrength = FadeStrength.md });
...
}
- 把 点击反馈 做成独立组件,不污染业务代码。
AnimationController
+FadeTransition
实现轻量「按下变暗 / 松手复原」的Material 触感。- 提供普通 (
normal
) 与半透明 (faded
) 两种模式,可在不同场景复用。
3.3 API 层:BuildContext
扩展
方法 | 用途 | 关键点 |
---|---|---|
showAdaptiveDialog |
包一层 AlertDialog.adaptive ,自动适配 iOS / Android 风格 |
同时暴露 titleTextStyle ,便于定制 |
showBottomModal |
对 showModalBottomSheet 包装 |
内置圆角 & DragHandle,可自由开关 |
showListOptionsModal |
带滚动的选项列表 | 组合 ModalOption + Tappable ;点击后 Navigator.pop 将选项回传 |
showImagePreview |
圆形图片预览弹窗 | AspectRatio + DecorationImage 让图片保持清晰 |
把重复的 shape / safeArea / isDismissible 等参数收拢在扩展方法中------调用端只关注内容和交互,让业务代码最小化。
3.4 真实场景示例
3.4.1 媒体发布菜单
dart
OutlinedButton(
child: const Text('自定义选项弹窗'),
onPressed: () {
context.showListOptionsModal(
title: '新建',
options: createMediaModalOptions(...),
).then((option) => option?.onTap(context));
},
);

3.4.2 圆形图片预览
dart
context.showImagePreview('https://picsum.photos/id/237/300/200');

Dialog
背景透明 (backgroundColor: Color(0x00000000)
),中间圆形头像带描边。- 手势关闭后自动回到原界面,适合社交 App 查看大图。
3.4.3 设置页选项
dart
context.showListOptionsModal(
options: [
ModalOption(child: const LocaleModalOption()),
ModalOption(child: const ThemeSelectorModalOption()),
ModalOption(child: const LogoutModalOption()),
],
);

每个 ModalOption
里直接塞自定义子组件,如 DropdownButton
语言/主题选择,实现**「边选边生效」**的即时交互体验。
4 小结
- 分层设计:把「数据模型」「交互动效」「对外 API」解耦,可复用也易维护。
- 动画小细节 (
Tappable
):让自定义弹窗拥有系统级触感,提升专业度。 - 危险操作显色 :像
distractiveColor
这样封装「红色」逻辑,可避免遗漏、风格不一致。
通过这些实践,基本能在 Flutter 中构建出自定义且易于迭代的弹窗体系,真正让「对话」成为强化用户体验的利器。
写在最后:本文代码有参考www.youtube.com/watch?v=xr5...