主题
Material 3 对话框样式规范
最后更新:2026-01-18 版本:v1.0 状态:✅ 强制执行
💡 提示:本文档是对话框的详细实战指南。如需查看完整的 Material 3 设计系统规范(包括文本、图标、颜色等基础规范),请参考 Material 3 设计系统
📋 目录
对话框类型
1. AlertDialog(警告/确认对话框)
使用场景:
- ⚠️ 确认删除操作
- ℹ️ 重要信息提示
- ✏️ 简单表单输入
特点:
- 标题 + 内容 + 按钮组
- 模态显示,必须关闭才能继续
- 通常包含"取消"和"确认"按钮
2. SimpleDialog(选择对话框)
使用场景:
- 📋 单选列表
- 🎨 选项选择(如主题选择)
特点:
- 标题 + 选项列表
- 点击选项后自动关闭
- 无需确认按钮
3. BottomSheet(底部弹出)
使用场景:
- 📱 移动端操作菜单
- 📋 更多选项
- 📝 表单输入
特点:
- 从底部滑入
- 可拖拽关闭
- 适合移动端
4. Dialog(自定义对话框)
使用场景:
- 🎯 复杂交互
- 📊 数据展示
- 🔧 特殊布局需求
特点:
- 完全自定义
- 需手动处理样式
样式规范
1. 圆角规范
对话框圆角:28px(Material 3 标准)
dart
// ✅ 正确:使用 AlertDialog 默认圆角
AlertDialog(
// shape: 默认 28px 圆角
title: const Text('确认操作'),
content: const Text('是否继续?'),
)
// ⚠️ 自定义时:
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
child: ...,
)对话框内元素圆角:12px
dart
// ✅ 对话框内的容器、按钮等使用 12px
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12), // 12px
),
child: Text(word),
)2. 内边距规范
| 区域 | 内边距 | 说明 |
|---|---|---|
| title | 24px(默认) | 标题区域 |
| content | 20px 水平,24px 垂直 | 内容区域 |
| actions | 8px(默认) | 按钮区域 |
dart
// ✅ 使用默认内边距(推荐)
AlertDialog(
title: const Text('标题'),
content: const Text('内容'),
actions: [...],
)
// ⚠️ 自定义时
AlertDialog(
titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
actionsPadding: const EdgeInsets.all(8),
...
)3. 颜色规范
背景色
dart
// ✅ 使用 Material 3 默认背景
AlertDialog(
backgroundColor: Theme.of(context).colorScheme.surface, // 默认值
...
)标题和内容颜色
dart
// ✅ 使用语义化颜色
AlertDialog(
title: Text(
'标题',
style: Theme.of(context).textTheme.titleLarge, // 自动使用 onSurface
),
content: Text(
'内容',
style: Theme.of(context).textTheme.bodyMedium, // 自动使用 onSurface
),
)4. 按钮样式
标准按钮组合
dart
actions: [
TextButton( // 取消按钮
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton( // 确认按钮
onPressed: _confirm,
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('确认'),
),
]危险操作
dart
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: _delete,
style: FilledButton.styleFrom(
backgroundColor: colorScheme.error, // 错误色
foregroundColor: colorScheme.onError,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('删除'),
),
]布局规范
1. 标题布局
纯文本标题
dart
title: const Text('确认操作')图标+文本标题
dart
title: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'警告',
style: theme.textTheme.titleLarge,
),
),
],
)2. 内容布局
简单文本
dart
content: const Text('是否继续执行此操作?')复杂内容
dart
content: Column(
mainAxisSize: MainAxisSize.min, // ⚠️ 必须设置
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 信息展示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: ...,
),
const SizedBox(height: 16),
// 输入框
TextField(...),
const SizedBox(height: 8),
// 提示文本
Text(...),
],
)3. 按钮布局
单个按钮
dart
actions: [
FilledButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
]两个按钮(标准)
dart
actions: [
TextButton( // 左侧:取消
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton( // 右侧:确认
onPressed: _confirm,
child: const Text('确认'),
),
]多个按钮(堆叠)
dart
actions: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FilledButton(
onPressed: _option1,
child: const Text('选项1'),
),
const SizedBox(height: 8),
FilledButton.tonal(
onPressed: _option2,
child: const Text('选项2'),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
],
),
]代码示例
示例1:简单确认对话框
dart
Future<bool?> showConfirmDialog({
required BuildContext context,
required String title,
required String message,
String confirmText = '确认',
String cancelText = '取消',
}) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(cancelText),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(confirmText),
),
],
),
);
}
// 使用
final confirmed = await showConfirmDialog(
context: context,
title: '删除确认',
message: '确定要删除这个项目吗?此操作无法撤销。',
confirmText: '删除',
);
if (confirmed == true) {
_delete();
}示例2:输入对话框
dart
class EditNoteDialog extends StatefulWidget {
final String word;
final String? initialNote;
const EditNoteDialog({
Key? key,
required this.word,
this.initialNote,
}) : super(key: key);
@override
State<EditNoteDialog> createState() => _EditNoteDialogState();
}
class _EditNoteDialogState extends State<EditNoteDialog> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialNote ?? '');
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
return AlertDialog(
title: Row(
children: [
Icon(
Icons.edit_note,
color: colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'编辑笔记',
style: theme.textTheme.titleLarge,
),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 单词显示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
Icons.menu_book,
size: 20,
color: colorScheme.onSecondaryContainer,
),
const SizedBox(width: 8),
Text(
widget.word,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onSecondaryContainer,
),
),
],
),
),
const SizedBox(height: 16),
// 输入框
TextField(
controller: _controller,
maxLines: 5,
decoration: InputDecoration(
hintText: '在此添加你的笔记...',
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.all(12),
counterText: '${_controller.text.length} 字符',
),
onChanged: (value) {
setState(() {}); // 更新字符计数
},
),
const SizedBox(height: 8),
// 提示文本
Text(
'💡 提示:笔记可以帮助你更好地记忆单词',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurface.withOpacity(0.6),
fontStyle: FontStyle.italic,
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
Navigator.pop(context, _controller.text.trim());
},
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('保存'),
),
],
);
}
}
// 使用
final note = await showDialog<String>(
context: context,
builder: (context) => EditNoteDialog(
word: 'example',
initialNote: '初始笔记',
),
);
if (note != null) {
_saveNote(note);
}示例3:选择对话框(SimpleDialog)
dart
class ThemeSelectionDialog extends ConsumerWidget {
const ThemeSelectionDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentTheme = ref.watch(themeModeProvider);
final colorScheme = Theme.of(context).colorScheme;
return SimpleDialog(
title: const Text('选择主题'),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
children: [
_ThemeOption(
title: '浅色',
subtitle: '明亮主题',
icon: Icons.light_mode_rounded,
isSelected: currentTheme == ThemeMode.light,
onTap: () {
ref.read(themeModeProvider.notifier).setThemeMode(ThemeMode.light);
Navigator.pop(context);
},
),
_ThemeOption(
title: '深色',
subtitle: '暗色主题',
icon: Icons.dark_mode_rounded,
isSelected: currentTheme == ThemeMode.dark,
onTap: () {
ref.read(themeModeProvider.notifier).setThemeMode(ThemeMode.dark);
Navigator.pop(context);
),
),
_ThemeOption(
title: '跟随系统',
subtitle: '自动切换',
icon: Icons.brightness_auto_rounded,
isSelected: currentTheme == ThemeMode.system,
onTap: () {
ref.read(themeModeProvider.notifier).resetToSystem();
Navigator.pop(context);
),
),
],
);
}
}
class _ThemeOption extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final bool isSelected;
final VoidCallback onTap;
const _ThemeOption({
required this.title,
required this.subtitle,
required this.icon,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12), // 12px 圆角
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? colorScheme.primary
: colorScheme.outline.withOpacity(0.2),
width: isSelected ? 2 : 1,
),
color: isSelected
? colorScheme.primaryContainer.withOpacity(0.2)
: Colors.transparent,
),
child: Row(
children: [
Icon(
icon,
color: isSelected ? colorScheme.primary : colorScheme.onSurface,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
color: isSelected ? colorScheme.primary : colorScheme.onSurface,
fontWeight: isSelected ? FontWeight.w600 : null,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
if (isSelected)
Icon(
Icons.check_circle_rounded,
color: colorScheme.primary,
size: 24,
),
],
),
),
),
),
);
}
}常见错误
❌ 错误1:对话框圆角不符合规范
dart
// ❌ 错误:自定义圆角为 16px
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), // 应该是 28px!
),
child: ...,
)
// ✅ 正确:使用 28px 圆角
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
child: ...,
)
// ✅ 更好:使用 AlertDialog 默认值
AlertDialog(
// 默认就是 28px
title: ...,
content: ...,
)❌ 错误2:content 未设置 mainAxisSize
dart
// ❌ 错误:Column 会占满高度
content: Column(
children: [
Text('Line 1'),
Text('Line 2'),
],
)
// ✅ 正确:使用 min
content: Column(
mainAxisSize: MainAxisSize.min, // ✅ 必须设置!
children: [
Text('Line 1'),
Text('Line 2'),
],
)❌ 错误3:按钮样式不一致
dart
// ❌ 错误:对话框按钮用了 ElevatedButton
actions: [
TextButton(...),
ElevatedButton(...), // ❌ 应该用 FilledButton!
]
// ✅ 正确
actions: [
TextButton(...),
FilledButton(
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
...
),
]❌ 错误4:硬编码内边距
dart
// ❌ 错误:不必要的 padding 覆盖
AlertDialog(
contentPadding: const EdgeInsets.all(20), // 破坏默认样式
content: ...,
)
// ✅ 正确:使用默认值
AlertDialog(
content: ...,
)快速参考
常用对话框模板
dart
// 简单确认对话框
AlertDialog(
title: const Text('标题'),
content: const Text('内容'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
FilledButton(
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () => Navigator.pop(context, true),
child: const Text('确认'),
),
],
)
// 危险操作确认
AlertDialog(
title: Row(
children: [
Icon(Icons.warning_amber_rounded, color: colorScheme.error),
const SizedBox(width: 8),
const Text('警告'),
],
),
content: const Text('此操作无法撤销!'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: colorScheme.error,
foregroundColor: colorScheme.onError,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
_delete();
Navigator.pop(context);
},
child: const Text('删除'),
),
],
)相关文档
维护者:Reading Vocab Helper Team 反馈:发现问题请提Issue