主题
Material 3 卡片组件样式规范
最后更新:2026-01-18 版本:v1.0 状态:✅ 强制执行
💡 提示:本文档是卡片组件的详细实战指南。如需查看完整的 Material 3 设计系统规范(包括文本、图标、颜色等基础规范),请参考 Material 3 设计系统
📋 目录
组件类型选择
决策树
展示内容类型?
├── 简单列表项(标题+副标题+图标)
│ └── ✅ ListTile
│
├── 卡片内容(图片+文字+操作)
│ └── ✅ Card + Column/Row
│
├── 可点击的卡片
│ └── ✅ Card + InkWell
│
├── 可展开的内容
│ └── ✅ ExpansionTile / ExpansionPanelList
│
├── 网格展示
│ └── ✅ GridView + Card
│
└── 信息面板
└── ✅ Container + decoration组件类型对照表
| 组件 | Material 3 组件 | 使用场景 | 示例 |
|---|---|---|---|
| 列表项 | ListTile | 设置列表、联系人列表、菜单项 | 设置项、书籍列表 |
| 卡片 | Card | 书籍卡片、单词卡片、信息面板 | 书籍详情、统计面板 |
| 可点击卡片 | Card + InkWell | 可交互的卡片项 | 书籍选择、词汇卡片 |
| 展开列表 | ExpansionTile | 可展开的菜单、FAQ、分组列表 | 设置分组、帮助文档 |
| 网格卡片 | GridView + Card | 图片网格、应用图标网格 | 书籍封面网格 |
Card 样式规范
1. 基础样式
Material 3 默认样式(推荐使用):
dart
// ✅ 正确:使用默认样式
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('标题', style: Theme.of(context).textTheme.titleMedium),
SizedBox(height: 8),
Text('内容'),
],
),
),
)自动样式:
- 背景色:
colorScheme.surface - 阴影:elevation = 1(轻微阴影)
- 圆角:12dp(Material 3 默认)
- 边框:无(使用阴影区分)
2. 圆角规范
统一使用 12px 圆角:
dart
// ✅ 正确:默认圆角(12px)
Card(
child: ...,
)
// ⚠️ 自定义圆角(需要时使用)
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // 明确12px
),
child: ...,
)
// ❌ 错误:圆角不一致
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // 不统一
),
child: ...,
)3. 阴影规范
Material 3 Elevation 系统:
| 场景 | Elevation | 视觉效果 |
|---|---|---|
| 默认卡片 | 1 | 轻微阴影(推荐) |
| 悬浮卡片 | 2 | 中等阴影 |
| 强调卡片 | 4 | 明显阴影 |
| 对话框 | 6 | 高层级阴影 |
dart
// ✅ 默认阴影(elevation: 1)
Card(
child: ...,
)
// ⚠️ 悬浮效果(elevation: 2)
Card(
elevation: 2,
child: ...,
)
// ❌ 避免过高的阴影(> 6)
Card(
elevation: 10, // 过于突出
child: ...,
)4. 内边距规范
标准内边距:16px
dart
// ✅ 正确:统一使用 16px 内边距
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: ...,
),
)
// ⚠️ 紧凑布局:12px
Card(
child: Padding(
padding: EdgeInsets.all(12),
child: ...,
),
)
// ⚠️ 宽松布局:24px
Card(
child: Padding(
padding: EdgeInsets.all(24),
child: ...,
),
)5. 颜色规范
背景色(自动)
dart
// ✅ 正确:使用默认背景色
Card(
// 自动使用 colorScheme.surface
child: ...,
)
// ⚠️ 自定义背景色(特殊场景)
Card(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: ...,
)
// ❌ 错误:硬编码颜色
Card(
color: Colors.grey[100], // 不适配深色模式
child: ...,
)边框颜色(可选)
dart
// ⚠️ 使用边框替代阴影(扁平设计)
Card(
elevation: 0, // 无阴影
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
borderRadius: BorderRadius.circular(12),
),
child: ...,
)6. 可点击卡片
使用 InkWell 包裹内容:
dart
// ✅ 正确:可点击卡片
Card(
clipBehavior: Clip.antiAlias, // 裁剪波纹效果
child: InkWell(
onTap: () {
// 点击逻辑
},
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('可点击卡片'),
SizedBox(height: 8),
Text('点击查看详情'),
],
),
),
),
)ListTile 样式规范
1. 基础用法
dart
// ✅ 标准 ListTile
ListTile(
leading: Icon(Icons.book), // 前置图标
title: Text('书名'), // 标题
subtitle: Text('作者:John Doe'), // 副标题
trailing: Icon(Icons.chevron_right), // 后置图标
onTap: () {
// 点击逻辑
},
)2. 图标规范
| 位置 | 尺寸 | 颜色 |
|---|---|---|
| leading | 24x24 dp | colorScheme.onSurfaceVariant |
| trailing | 24x24 dp | colorScheme.onSurfaceVariant |
dart
// ✅ 正确:使用默认图标颜色
ListTile(
leading: Icon(Icons.book), // 自动使用 onSurfaceVariant
title: Text('书名'),
trailing: Icon(Icons.chevron_right),
)
// ❌ 错误:硬编码图标颜色
ListTile(
leading: Icon(Icons.book, color: Colors.grey), // 不适配主题
title: Text('书名'),
)3. 文本样式
| 元素 | 样式 | 颜色 |
|---|---|---|
| title | titleMedium (16sp, w500) | onSurface |
| subtitle | bodyMedium (14sp, w400) | onSurfaceVariant |
dart
// ✅ 正确:使用默认文本样式
ListTile(
title: Text('标题'),
subtitle: Text('副标题'),
)
// ⚠️ 自定义样式(需要时使用)
ListTile(
title: Text(
'标题',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Text('副标题'),
)4. 带图片的 ListTile
dart
// ✅ 圆形头像
ListTile(
leading: CircleAvatar(
radius: 24,
backgroundImage: AssetImage('assets/book-cover.jpg'),
),
title: Text('书名'),
subtitle: Text('作者'),
)
// ✅ 方形缩略图
ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(6), // 轻微圆角
child: Image.asset(
'assets/book-cover.jpg',
width: 50,
height: 64,
fit: BoxFit.cover,
),
),
title: Text('书名'),
subtitle: Text('作者'),
)5. 三行 ListTile
dart
// ✅ 三行布局
ListTile(
isThreeLine: true,
leading: Icon(Icons.book),
title: Text('书名'),
subtitle: Text(
'作者:John Doe\n分类:Fiction',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: Icon(Icons.chevron_right),
)布局规范
1. 卡片间距
垂直间距:12px(Card 之间)
dart
ListView(
padding: EdgeInsets.all(16),
children: [
Card(child: ...),
SizedBox(height: 12), // 卡片间距
Card(child: ...),
SizedBox(height: 12),
Card(child: ...),
],
)2. 卡片布局模式
垂直滚动列表
dart
ListView.builder(
padding: EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(bottom: 12),
child: Card(
child: ListTile(
title: Text(items[index]),
),
),
);
},
)网格布局
dart
GridView.builder(
padding: EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 2列
crossAxisSpacing: 12, // 水平间距
mainAxisSpacing: 12, // 垂直间距
childAspectRatio: 0.7, // 宽高比
),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
child: Column(
children: [
Image.asset(items[index].cover),
Padding(
padding: EdgeInsets.all(8),
child: Text(items[index].title),
),
],
),
);
},
)3. 卡片内容布局
图片 + 文字(垂直)
dart
Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图片
Image.asset(
'assets/book-cover.jpg',
height: 180,
width: double.infinity,
fit: BoxFit.cover,
),
// 文字内容
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'书名',
style: Theme.of(context).textTheme.titleMedium,
),
SizedBox(height: 4),
Text(
'作者:John Doe',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
)图片 + 文字(水平)
dart
Card(
child: Padding(
padding: EdgeInsets.all(12),
child: Row(
children: [
// 封面
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.asset(
'assets/book-cover.jpg',
width: 50,
height: 70,
fit: BoxFit.cover,
),
),
SizedBox(width: 12),
// 文字
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'书名',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 4),
Text(
'作者:John Doe',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
// 操作按钮
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () {},
),
],
),
),
)代码示例
示例1:书籍列表卡片
dart
class BookListCard extends StatelessWidget {
final String title;
final String author;
final String coverPath;
final VoidCallback onTap;
const BookListCard({
required this.title,
required this.author,
required this.coverPath,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
// 封面
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.asset(
coverPath,
width: 50,
height: 70,
fit: BoxFit.cover,
),
),
const SizedBox(width: 12),
// 书籍信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'作者:$author',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
// 箭头图标
Icon(
Icons.chevron_right,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
),
),
),
);
}
}
// 使用
BookListCard(
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
coverPath: 'assets/gatsby.jpg',
onTap: () {
// 导航到书籍详情
},
)示例2:统计卡片
dart
class StatisticsCard extends StatelessWidget {
final String title;
final String value;
final IconData icon;
final Color? color;
const StatisticsCard({
required this.title,
required this.value,
required this.icon,
this.color,
});
@override
Widget build(BuildContext context) {
final cardColor = color ?? Theme.of(context).colorScheme.primary;
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图标
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cardColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: cardColor,
size: 32,
),
),
const SizedBox(height: 12),
// 标题
Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
// 数值
Text(
value,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: cardColor,
),
),
],
),
),
);
}
}
// 使用
Row(
children: [
Expanded(
child: StatisticsCard(
title: '学习中',
value: '128',
icon: Icons.school,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 12),
Expanded(
child: StatisticsCard(
title: '已掌握',
value: '342',
icon: Icons.check_circle,
color: Colors.green,
),
),
],
)示例3:可展开卡片
dart
class ExpandableCard extends StatefulWidget {
final String title;
final String summary;
final Widget content;
const ExpandableCard({
required this.title,
required this.summary,
required this.content,
});
@override
_ExpandableCardState createState() => _ExpandableCardState();
}
class _ExpandableCardState extends State<ExpandableCard> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
ListTile(
title: Text(widget.title),
subtitle: Text(widget.summary),
trailing: IconButton(
icon: Icon(
_isExpanded ? Icons.expand_less : Icons.expand_more,
),
onPressed: () {
setState(() => _isExpanded = !_isExpanded);
},
),
),
if (_isExpanded)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: widget.content,
),
],
),
);
}
}
// 使用
ExpandableCard(
title: 'CEFR A1 级别',
summary: '初学者,掌握基础词汇',
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('• 词汇量:500-1000'),
Text('• 能够理解简单句子'),
Text('• 日常对话基础'),
],
),
)常见错误
❌ 错误1:圆角不统一
dart
// ❌ 错误:自定义圆角不一致
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // 不统一
),
child: ...,
)
// ✅ 正确:使用默认圆角(12px)
Card(
child: ...,
)❌ 错误2:内边距不一致
dart
// ❌ 错误:内边距混乱
Card(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 15),
child: ...,
),
)
// ✅ 正确:统一16px内边距
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: ...,
),
)❌ 错误3:硬编码颜色
dart
// ❌ 错误:硬编码背景色
Card(
color: Colors.grey[100],
child: ...,
)
// ✅ 正确:使用主题颜色
Card(
// 自动使用 colorScheme.surface
child: ...,
)
// ⚠️ 特殊场景:使用主题颜色
Card(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: ...,
)❌ 错误4:ListTile 图标硬编码
dart
// ❌ 错误:硬编码图标颜色
ListTile(
leading: Icon(Icons.book, color: Colors.grey),
title: Text('书名'),
)
// ✅ 正确:使用默认颜色
ListTile(
leading: Icon(Icons.book), // 自动使用 onSurfaceVariant
title: Text('书名'),
)❌ 错误5:过高的阴影
dart
// ❌ 错误:阴影过高
Card(
elevation: 10, // 过于突出
child: ...,
)
// ✅ 正确:使用默认或低阴影
Card(
elevation: 1, // 默认(可省略)
child: ...,
)
Card(
elevation: 2, // 悬浮效果
child: ...,
)快速参考
常用卡片模板
dart
// 基础卡片
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('内容'),
),
)
// 可点击卡片
Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.all(16),
child: Text('可点击内容'),
),
),
)
// ListTile 模板
ListTile(
leading: Icon(Icons.book),
title: Text('标题'),
subtitle: Text('副标题'),
trailing: Icon(Icons.chevron_right),
onTap: () {},
)
// 带边框的卡片(扁平设计)
Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Text('内容'),
),
)相关文档
维护者:Reading Vocab Helper Team 反馈:发现问题请提Issue