Skip to content

Material 3 对话框样式规范

最后更新:2026-01-18 版本:v1.0 状态:✅ 强制执行

💡 提示:本文档是对话框的详细实战指南。如需查看完整的 Material 3 设计系统规范(包括文本、图标、颜色等基础规范),请参考 Material 3 设计系统


📋 目录

  1. 对话框类型
  2. 样式规范
  3. 布局规范
  4. 代码示例
  5. 常见错误

对话框类型

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. 内边距规范

区域内边距说明
title24px(默认)标题区域
content20px 水平,24px 垂直内容区域
actions8px(默认)按钮区域
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