主题
Material 3 表单组件样式规范
最后更新:2026-01-18 版本:v1.0 状态:✅ 强制执行
💡 提示:本文档是表单组件的详细实战指南。如需查看完整的 Material 3 设计系统规范(包括文本、图标、颜色等基础规范),请参考 Material 3 设计系统
📋 目录
组件类型选择
决策树
输入类型?
├── 文本输入(单行)
│ └── ✅ TextField(outlined 样式)
│
├── 文本输入(多行)
│ └── ✅ TextField(maxLines: null, minLines: 3)
│
├── 单选(2-4个选项)
│ └── ✅ Radio + RadioListTile
│
├── 单选(> 5个选项)
│ └── ✅ DropdownButton / PopupMenuButton
│
├── 多选(少量选项)
│ └── ✅ Checkbox + CheckboxListTile
│
├── 多选(大量选项)
│ └── ✅ FilterChip / ChoiceChip
│
└── 开关(布尔值)
└── ✅ Switch / SwitchListTile组件类型对照表
| 组件 | Material 3 组件 | 使用场景 | 示例 |
|---|---|---|---|
| 文本输入 | TextField | 用户名、密码、书名、笔记 | "请输入书名" |
| 多行输入 | TextField(maxLines: null) | 备注、描述、长文本 | "添加笔记..." |
| 单选 | Radio + RadioListTile | CEFR级别选择、排序方式 | A1/A2/B1... |
| 下拉选择 | DropdownButton | 书籍分类、语言选择 | Fiction/Non-fiction |
| 多选 | Checkbox + CheckboxListTile | 标签选择、权限设置 | 启用发音、显示例句 |
| 芯片选择 | FilterChip / ChoiceChip | 标签、分类筛选 | CEFR级别芯片 |
| 开关 | Switch + SwitchListTile | 功能开关、设置项 | 启用通知、深色模式 |
TextField 样式规范
1. 基础样式(Outlined)
统一使用 Outlined 样式(Material 3 推荐):
dart
// ✅ 正确:Outlined 样式
TextField(
decoration: InputDecoration(
labelText: '书名',
hintText: '请输入书名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), // 圆角12px
),
),
)
// ❌ 错误:Filled 样式(旧设计)
TextField(
decoration: InputDecoration(
labelText: '书名',
filled: true, // 不推荐
),
)2. 圆角规范
统一使用 12px 圆角(与按钮、容器保持一致):
dart
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), // ✅ 12px
),
),
)3. 颜色规范
边框颜色(自动)
dart
// ✅ 正确:使用默认颜色(自动适配主题)
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), // 未聚焦:outline
// 聚焦时自动变为 primary
),
)颜色映射:
- 未聚焦:
colorScheme.outline(灰色边框) - 聚焦:
colorScheme.primary(蓝色边框) - 错误:
colorScheme.error(红色边框) - 禁用:
colorScheme.onSurface.withOpacity(0.38)
文本颜色(自动)
dart
// ✅ 正确:自动使用主题颜色
TextField(
// 输入文字:onSurface(自动)
// label:onSurfaceVariant(自动)
// hint:onSurfaceVariant.withOpacity(0.6)(自动)
)4. 标签和提示文字
dart
// ✅ 推荐:同时使用 labelText 和 hintText
TextField(
decoration: InputDecoration(
labelText: '书名', // 浮动标签
hintText: '请输入书名', // 占位提示
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
// ⚠️ 可选:仅使用 labelText(简洁)
TextField(
decoration: InputDecoration(
labelText: '用户名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)5. 辅助文字和错误提示
dart
// ✅ 正确:辅助文字 + 错误提示
TextField(
decoration: InputDecoration(
labelText: '密码',
hintText: '至少8位字符',
helperText: '密码长度至少8位', // 辅助文字
errorText: _errorText, // 错误提示
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
// 动态错误提示
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 8) {
return '密码长度至少8位';
}
return null;
}6. 前缀和后缀图标
dart
// ✅ 推荐:使用 prefixIcon 和 suffixIcon
TextField(
decoration: InputDecoration(
labelText: '搜索',
hintText: '输入关键词...',
prefixIcon: Icon(Icons.search), // 前缀图标
suffixIcon: IconButton( // 后缀按钮
icon: Icon(Icons.clear),
onPressed: _clearText,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)7. 多行输入
dart
// ✅ 多行文本输入
TextField(
maxLines: null, // 无限行
minLines: 3, // 最少3行
decoration: InputDecoration(
labelText: '笔记',
hintText: '添加笔记...',
alignLabelWithHint: true, // 标签与输入对齐
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)选择控件规范
1. Radio(单选按钮)
基础用法
dart
// ✅ 推荐:使用 RadioListTile
RadioListTile<String>(
title: Text('A1 - 初学者'),
value: 'A1',
groupValue: _selectedCEFR,
onChanged: (value) => setState(() => _selectedCEFR = value),
activeColor: Theme.of(context).colorScheme.primary, // 选中颜色
)自定义样式
dart
// 水平排列的 Radio 组
Row(
children: [
Radio<String>(
value: 'A1',
groupValue: _selectedCEFR,
onChanged: (value) => setState(() => _selectedCEFR = value),
),
Text('A1'),
SizedBox(width: 16),
Radio<String>(
value: 'A2',
groupValue: _selectedCEFR,
onChanged: (value) => setState(() => _selectedCEFR = value),
),
Text('A2'),
],
)2. Checkbox(多选框)
基础用法
dart
// ✅ 推荐:使用 CheckboxListTile
CheckboxListTile(
title: Text('启用发音'),
subtitle: Text('点击单词时自动播放发音'),
value: _enablePronunciation,
onChanged: (value) => setState(() => _enablePronunciation = value ?? false),
activeColor: Theme.of(context).colorScheme.primary,
)三态复选框
dart
// 三态复选框(true/false/null)
CheckboxListTile(
title: Text('全选'),
tristate: true, // 启用三态
value: _allSelected, // true/false/null
onChanged: (value) => setState(() => _allSelected = value),
)3. Switch(开关)
基础用法
dart
// ✅ 推荐:使用 SwitchListTile
SwitchListTile(
title: Text('深色模式'),
subtitle: Text('使用深色主题'),
value: _darkMode,
onChanged: (value) => setState(() => _darkMode = value),
activeColor: Theme.of(context).colorScheme.primary,
)自定义样式
dart
// 紧凑样式(标题 + 开关)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('启用通知'),
Switch(
value: _notificationsEnabled,
onChanged: (value) => setState(() => _notificationsEnabled = value),
activeColor: Theme.of(context).colorScheme.primary,
),
],
)4. DropdownButton(下拉选择)
dart
// ✅ 下拉选择框
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: '书籍分类',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
value: _selectedCategory,
items: [
DropdownMenuItem(value: 'fiction', child: Text('Fiction')),
DropdownMenuItem(value: 'non-fiction', child: Text('Non-fiction')),
DropdownMenuItem(value: 'textbook', child: Text('Textbook')),
],
onChanged: (value) => setState(() => _selectedCategory = value),
)5. Chip(芯片选择)
FilterChip(多选)
dart
// ✅ FilterChip 用于多选标签
Wrap(
spacing: 8,
children: [
FilterChip(
label: Text('A1'),
selected: _selectedLevels.contains('A1'),
onSelected: (selected) {
setState(() {
if (selected) {
_selectedLevels.add('A1');
} else {
_selectedLevels.remove('A1');
}
});
},
),
FilterChip(
label: Text('A2'),
selected: _selectedLevels.contains('A2'),
onSelected: (selected) {
setState(() {
if (selected) {
_selectedLevels.add('A2');
} else {
_selectedLevels.remove('A2');
}
});
},
),
],
)ChoiceChip(单选)
dart
// ✅ ChoiceChip 用于单选标签
Wrap(
spacing: 8,
children: [
ChoiceChip(
label: Text('全部'),
selected: _selectedFilter == 'all',
onSelected: (selected) {
if (selected) setState(() => _selectedFilter = 'all');
},
),
ChoiceChip(
label: Text('学习中'),
selected: _selectedFilter == 'learning',
onSelected: (selected) {
if (selected) setState(() => _selectedFilter = 'learning');
},
),
],
)表单布局规范
1. 垂直间距
| 场景 | 间距 |
|---|---|
| 表单项之间 | 16px |
| 分组标题与表单项 | 8px |
| 表单与提交按钮 | 24px |
dart
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(...),
SizedBox(height: 16), // 表单项间距
TextField(...),
SizedBox(height: 16),
TextField(...),
SizedBox(height: 24), // 表单与按钮间距
FilledButton(...),
],
)2. 表单宽度
全宽表单(移动端)
dart
Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: '书名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
SizedBox(height: 16),
SizedBox(
width: double.infinity, // 全宽按钮
height: 48,
child: FilledButton(
onPressed: _submit,
child: Text('提交'),
),
),
],
),
)3. 分组布局
dart
// ✅ 使用 SectionLabel 分组
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionLabel('基本信息'),
SizedBox(height: 8),
TextField(
decoration: InputDecoration(
labelText: '书名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '作者',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
SizedBox(height: 24),
SectionLabel('高级设置'),
SizedBox(height: 8),
SwitchListTile(...),
CheckboxListTile(...),
],
)代码示例
示例1:完整登录表单
dart
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 邮箱输入
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: '邮箱',
hintText: 'example@email.com',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
if (!value.contains('@')) {
return '请输入有效的邮箱地址';
}
return null;
},
),
SizedBox(height: 16),
// 密码输入
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: '密码',
hintText: '至少8位字符',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() => _obscurePassword = !_obscurePassword);
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 8) {
return '密码长度至少8位';
}
return null;
},
),
SizedBox(height: 24),
// 提交按钮
SizedBox(
height: 48,
child: FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_submit();
}
},
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text('登录'),
),
),
],
),
),
);
}
void _submit() {
// 提交逻辑
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}示例2:书籍创建表单
dart
class CreateBookForm extends StatefulWidget {
@override
_CreateBookFormState createState() => _CreateBookFormState();
}
class _CreateBookFormState extends State<CreateBookForm> {
final _titleController = TextEditingController();
final _authorController = TextEditingController();
String _selectedCategory = 'fiction';
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 基本信息
SectionLabel('基本信息'),
SizedBox(height: 8),
TextField(
controller: _titleController,
decoration: InputDecoration(
labelText: '书名',
hintText: '请输入书名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
SizedBox(height: 16),
TextField(
controller: _authorController,
decoration: InputDecoration(
labelText: '作者',
hintText: '请输入作者名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
SizedBox(height: 16),
// 分类选择
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: '分类',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
value: _selectedCategory,
items: [
DropdownMenuItem(value: 'fiction', child: Text('Fiction')),
DropdownMenuItem(value: 'non-fiction', child: Text('Non-fiction')),
DropdownMenuItem(value: 'textbook', child: Text('Textbook')),
],
onChanged: (value) {
setState(() => _selectedCategory = value!);
},
),
SizedBox(height: 24),
// 保存按钮
SizedBox(
width: double.infinity,
height: 48,
child: FilledButton(
onPressed: _save,
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text('保存'),
),
),
],
),
);
}
void _save() {
// 保存逻辑
}
@override
void dispose() {
_titleController.dispose();
_authorController.dispose();
super.dispose();
}
}常见错误
❌ 错误1:使用 Filled 样式
dart
// ❌ 错误:Filled 样式(Material 2 旧样式)
TextField(
decoration: InputDecoration(
labelText: '书名',
filled: true,
fillColor: Colors.grey[200],
),
)
// ✅ 正确:Outlined 样式(Material 3 推荐)
TextField(
decoration: InputDecoration(
labelText: '书名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)❌ 错误2:圆角不统一
dart
// ❌ 错误:圆角不一致
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // 8px
),
),
)
// ✅ 正确:统一使用12px圆角
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), // 12px
),
),
)❌ 错误3:硬编码颜色
dart
// ❌ 错误:硬编码颜色
TextField(
decoration: InputDecoration(
labelStyle: TextStyle(color: Colors.blue),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
),
),
)
// ✅ 正确:使用主题颜色(自动)
TextField(
decoration: InputDecoration(
// labelStyle 和 focusedBorder 颜色自动使用 colorScheme.primary
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)❌ 错误4:表单项间距不一致
dart
// ❌ 错误:间距混乱
Column(
children: [
TextField(...),
SizedBox(height: 10), // 不一致
TextField(...),
SizedBox(height: 20), // 不一致
TextField(...),
],
)
// ✅ 正确:统一16px间距
Column(
children: [
TextField(...),
SizedBox(height: 16), // 统一
TextField(...),
SizedBox(height: 16), // 统一
TextField(...),
],
)❌ 错误5:未使用 Form 验证
dart
// ❌ 错误:手动验证,代码冗长
TextField(
controller: _controller,
onChanged: (value) {
setState(() {
if (value.isEmpty) {
_errorText = '请输入内容';
} else {
_errorText = null;
}
});
},
decoration: InputDecoration(
errorText: _errorText,
),
)
// ✅ 正确:使用 Form + TextFormField
Form(
key: _formKey,
child: TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入内容';
}
return null;
},
),
)快速参考
常用表单组件模板
dart
// TextField 模板
TextField(
decoration: InputDecoration(
labelText: '标签',
hintText: '提示文字',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
// TextFormField 模板(带验证)
TextFormField(
decoration: InputDecoration(
labelText: '标签',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入内容';
}
return null;
},
)
// SwitchListTile 模板
SwitchListTile(
title: Text('标题'),
subtitle: Text('描述'),
value: _value,
onChanged: (value) => setState(() => _value = value),
activeColor: Theme.of(context).colorScheme.primary,
)
// CheckboxListTile 模板
CheckboxListTile(
title: Text('标题'),
value: _value,
onChanged: (value) => setState(() => _value = value ?? false),
activeColor: Theme.of(context).colorScheme.primary,
)相关文档
维护者:Reading Vocab Helper Team 反馈:发现问题请提Issue