Flutter框架适配鸿蒙:Card交互效果
Card组件不仅能展示静态内容,还可以通过结合各种交互组件实现丰富的用户交互体验。本文将详细介绍Card组件的交互设计模式、手势识别、点击反馈、状态切换等交互特性。
·

Card组件不仅能展示静态内容,还可以通过结合各种交互组件实现丰富的用户交互体验。本文将详细介绍Card组件的交互设计模式、手势识别、点击反馈、状态切换等交互特性。
一、Card交互模式
Card组件支持多种交互模式,可以根据应用场景选择合适的交互方式。
1.1 交互模式分类
1.2 交互场景对比
| 交互类型 | 适用场景 | 实现难度 | 用户体验 | 推荐指数 |
|---|---|---|---|---|
| 点击卡片 | 进入详情页 | 简单 | 直观 | ⭐⭐⭐⭐⭐ |
| 点击按钮 | 执行操作 | 简单 | 明确 | ⭐⭐⭐⭐⭐ |
| 滑动删除 | 快捷操作 | 中等 | 高效 | ⭐⭐⭐⭐ |
| 拖拽排序 | 个性化布局 | 较难 | 有趣 | ⭐⭐⭐ |
| 长按菜单 | 更多选项 | 中等 | 便捷 | ⭐⭐⭐⭐ |
二、点击交互
2.1 整个Card可点击
最简单的交互方式是让整个Card响应点击:
class ClickableCard extends StatelessWidget {
final String title;
final String subtitle;
final VoidCallback onTap;
const ClickableCard({
super.key,
required this.title,
required this.subtitle,
required this.onTap,
});
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onTap,
splashColor: Colors.blue.withOpacity(0.3),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
subtitle,
style: TextStyle(
color: Colors.grey.shade600,
),
),
],
),
),
),
);
}
}
2.2 Card内部按钮交互
在Card内部添加操作按钮:
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'操作卡片',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
const Text('这是一个带有操作按钮的卡片'),
const SizedBox(height: 16),
Row(
children: [
ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('点击了确认按钮')),
);
},
icon: const Icon(Icons.check),
label: const Text('确认'),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('点击了取消按钮')),
);
},
icon: const Icon(Icons.close),
label: const Text('取消'),
),
],
),
],
),
),
)
2.3 点击反馈效果
不同状态的点击反馈对比:
| 状态类型 | 实现方式 | 视觉效果 | 触觉反馈 |
|---|---|---|---|
| 点击波纹 | InkWell | 水波纹扩散 | 轻微震动 |
| 按下效果 | Listener | 颜色变化 | 无 |
| 悬停效果 | MouseRegion | 边框高亮 | 无 |
| 长按效果 | GestureDetector | 缩放效果 | 震动 |
Card(
child: Material(
child: InkWell(
onTap: () {
print('Card被点击');
},
onTapDown: (_) {
print('按下');
},
onTapUp: (_) {
print('抬起');
},
onLongPress: () {
print('长按');
},
splashColor: Theme.of(context).primaryColor.withOpacity(0.3),
highlightColor: Theme.of(context).primaryColor.withOpacity(0.1),
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('带完整点击反馈的Card'),
),
),
),
)
三、滑动交互
3.1 Dismissible可滑动删除
使用Dismissible实现滑动操作:
class DismissibleCard extends StatelessWidget {
final String title;
final VoidCallback onDelete;
const DismissibleCard({
super.key,
required this.title,
required this.onDelete,
});
Widget build(BuildContext context) {
return Dismissible(
key: Key(title),
onDismissed: (direction) {
onDelete();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$title 已删除')),
);
},
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
secondaryBackground: Container(
color: Colors.blue,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 20),
child: const Icon(
Icons.archive,
color: Colors.white,
),
),
child: Card(
child: ListTile(
title: Text(title),
trailing: const Icon(Icons.drag_handle),
),
),
);
}
}
3.2 滑动操作按钮
实现左右滑动显示操作按钮:
class SwipeableCard extends StatefulWidget {
final String title;
const SwipeableCard({super.key, required this.title});
State<SwipeableCard> createState() => _SwipeableCardState();
}
class _SwipeableCardState extends State<SwipeableCard> {
bool _isExpanded = false;
Widget build(BuildContext context) {
return Stack(
children: [
// 背景操作按钮
Positioned.fill(
child: Row(
children: [
Expanded(
child: Container(
color: Colors.red,
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
),
Expanded(
child: Container(
color: Colors.blue,
child: const Icon(
Icons.archive,
color: Colors.white,
),
),
),
],
),
),
// 前景卡片
Dismissible(
key: Key(widget.title),
onDismissed: (direction) {
if (direction == DismissDirection.startToEnd) {
print('归档');
} else {
print('删除');
}
},
child: Card(
child: ListTile(
title: Text(widget.title),
subtitle: Text('左滑归档,右滑删除'),
trailing: const Icon(Icons.drag_handle),
),
),
),
],
);
}
}
四、拖拽交互
4.1 Draggable可拖拽卡片
实现可拖拽的Card:
class DraggableCard extends StatelessWidget {
final String title;
final String data;
const DraggableCard({
super.key,
required this.title,
required this.data,
});
Widget build(BuildContext context) {
return Draggable<String>(
data: data,
feedback: Material(
elevation: 8,
borderRadius: BorderRadius.circular(8),
child: Card(
child: Container(
width: 200,
padding: const EdgeInsets.all(16),
child: Text(
title,
style: const TextStyle(color: Colors.black),
),
),
),
),
childWhenDragging: Card(
color: Colors.grey.shade300,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
title,
style: TextStyle(
color: Colors.grey.shade600,
),
),
),
),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.drag_handle),
const SizedBox(width: 12),
Expanded(
child: Text(title),
),
const Icon(Icons.more_vert),
],
),
),
),
);
}
}
4.2 DragTarget放置目标
class DropZone extends StatefulWidget {
const DropZone({super.key});
State<DropZone> createState() => _DropZoneState();
}
class _DropZoneState extends State<DropZone> {
final List<String> _items = [];
bool _isDraggingOver = false;
Widget build(BuildContext context) {
return DragTarget<String>(
onAccept: (data) {
setState(() {
_items.add(data);
_isDraggingOver = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$data 已添加')),
);
},
onWillAccept: (data) {
setState(() {
_isDraggingOver = true;
});
return true;
},
onLeave: (data) {
setState(() {
_isDraggingOver = false;
});
},
builder: (context, candidateData, rejectedData) {
return Card(
color: _isDraggingOver
? Colors.green.shade200
: Colors.grey.shade100,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Icon(
_isDraggingOver ? Icons.cloud_upload : Icons.cloud_download,
size: 48,
color: _isDraggingOver ? Colors.green : Colors.grey,
),
const SizedBox(height: 12),
Text(
_isDraggingOver ? '松开添加' : '拖拽到这里',
style: TextStyle(
color: _isDraggingOver ? Colors.green : Colors.grey.shade600,
fontSize: 16,
),
),
if (_items.isNotEmpty) ...[
const SizedBox(height: 16),
const Divider(),
..._items.map((item) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text('• $item'),
)),
],
],
),
),
);
},
);
}
}
五、展开折叠交互
5.1 ExpansionTile卡片
使用ExpansionTile实现可展开的Card:
class ExpandableCard extends StatelessWidget {
final String title;
final String description;
final List<String> details;
const ExpandableCard({
super.key,
required this.title,
required this.description,
required this.details,
});
Widget build(BuildContext context) {
return Card(
child: ExpansionTile(
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(description),
leading: const Icon(Icons.expand_more),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details
.map((detail) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('• ', style: TextStyle(fontSize: 16)),
Expanded(child: Text(detail)),
],
),
))
.toList(),
),
),
],
),
);
}
}
5.2 动画展开效果
自定义展开动画:
class AnimatedExpandableCard extends StatefulWidget {
final String title;
final String content;
const AnimatedExpandableCard({
super.key,
required this.title,
required this.content,
});
State<AnimatedExpandableCard> createState() => _AnimatedExpandableCardState();
}
class _AnimatedExpandableCardState extends State<AnimatedExpandableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _isExpanded = false;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleExpand() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
ListTile(
title: Text(widget.title),
trailing: AnimatedRotation(
turns: _animation.value * 0.5,
duration: const Duration(milliseconds: 300),
child: const Icon(Icons.expand_more),
),
onTap: _toggleExpand,
),
SizeTransition(
sizeFactor: _animation,
axisAlignment: -1,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(widget.content),
),
),
],
),
);
}
}
六、多选交互
6.1 带复选框的Card
class SelectableCard extends StatefulWidget {
final String title;
final ValueChanged<bool?> onSelected;
const SelectableCard({
super.key,
required this.title,
required this.onSelected,
});
State<SelectableCard> createState() => _SelectableCardState();
}
class _SelectableCardState extends State<SelectableCard> {
bool _isSelected = false;
Widget build(BuildContext context) {
return Card(
color: _isSelected ? Colors.blue.shade50 : null,
child: CheckboxListTile(
value: _isSelected,
onChanged: (value) {
setState(() {
_isSelected = value ?? false;
});
widget.onSelected(value);
},
title: Text(
widget.title,
style: TextStyle(
fontWeight: _isSelected ? FontWeight.bold : FontWeight.normal,
),
),
subtitle: Text(_isSelected ? '已选中' : '点击选择'),
controlAffinity: ListTileControlAffinity.leading,
),
);
}
}
6.2 批量操作模式
class BatchSelectionCard extends StatefulWidget {
final String title;
const BatchSelectionCard({super.key, required this.title});
State<BatchSelectionCard> createState() => _BatchSelectionCardState();
}
class _BatchSelectionCardState extends State<BatchSelectionCard> {
bool _isInSelectionMode = false;
bool _isSelected = false;
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: _isInSelectionMode
? Checkbox(
value: _isSelected,
onChanged: (value) {
setState(() {
_isSelected = value ?? false;
});
},
)
: const Icon(Icons.description),
title: Text(widget.title),
subtitle: Text(_isInSelectionMode ? '批量操作模式' : '长按进入批量操作'),
trailing: _isInSelectionMode
? null
: const Icon(Icons.more_vert),
onLongPress: () {
setState(() {
_isInSelectionMode = true;
});
},
),
);
}
}
七、手势识别
7.1 多手势支持
class MultiGestureCard extends StatelessWidget {
const MultiGestureCard({super.key});
Widget build(BuildContext context) {
return Card(
child: GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('点击')),
);
},
onDoubleTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('双击')),
);
},
onLongPress: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('长按')),
);
},
onHorizontalDragEnd: (details) {
if (details.primaryVelocity != null) {
if (details.primaryVelocity! > 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('向右滑动')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('向左滑动')),
);
}
}
},
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('支持点击、双击、长按、滑动'),
),
),
);
}
}
7.2 手势冲突处理
class PriorityGestureCard extends StatelessWidget {
const PriorityGestureCard({super.key});
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
// 长按手势优先级最高
LongPressGestureRecognizer:
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(duration: const Duration(milliseconds: 500)),
(LongPressGestureRecognizer instance) {
instance.onLongPress = () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('长按操作')),
);
};
},
),
// 点击手势
TapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(),
(TapGestureRecognizer instance) {
instance.onTap = () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('点击操作')),
);
};
},
),
},
child: const Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('手势冲突处理示例'),
),
),
);
}
}
八、完整交互示例
class CardInteractionExample extends StatelessWidget {
const CardInteractionExample({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Card交互效果'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text(
'点击交互',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
InkWell(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Card被点击了')),
);
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.touch_app, color: Colors.blue),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'可点击Card',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text('点击整个Card', style: TextStyle(color: Colors.grey.shade600)),
],
),
),
const Icon(Icons.arrow_forward_ios),
],
),
),
),
),
const SizedBox(height: 16),
const Text(
'按钮交互',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'带操作按钮的Card',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Row(
children: [
ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('确认')),
);
},
icon: const Icon(Icons.check),
label: const Text('确认'),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('取消')),
);
},
icon: const Icon(Icons.close),
label: const Text('取消'),
),
],
),
],
),
),
),
const SizedBox(height: 16),
const Text(
'展开折叠',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Card(
child: ExpansionTile(
title: const Text(
'可展开的Card',
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: const Text('点击展开查看详情'),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('• 展开内容第一项'),
Text('• 展开内容第二项'),
Text('• 展开内容第三项'),
],
),
),
],
),
),
const SizedBox(height: 16),
const Text(
'复选框交互',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Card(
child: CheckboxListTile(
value: false,
onChanged: (value) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(value == true ? '已选中' : '未选中')),
);
},
title: const Text('可选择的Card'),
subtitle: const Text('点击复选框选择'),
controlAffinity: ListTileControlAffinity.leading,
),
),
const SizedBox(height: 16),
const Text(
'手势识别',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('单次点击')),
);
},
onDoubleTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('双击')),
);
},
onLongPress: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('长按')),
);
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Icon(Icons.gesture, size: 48, color: Colors.blue),
const SizedBox(height: 12),
const Text(
'多手势Card',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text('支持单击、双击、长按', style: TextStyle(color: Colors.grey.shade600)),
],
),
),
),
),
const SizedBox(height: 16),
const Text(
'拖拽卡片',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
DraggableCard(title: '拖拽我', data: '可拖拽数据'),
],
),
);
}
}
九、交互设计最佳实践
交互设计原则
| 原则 | 说明 | 实例 |
|---|---|---|
| 明确性 | 交互意图清晰 | 使用图标+文字 |
| 反馈性 | 即时视觉反馈 | 点击波纹效果 |
| 一致性 | 保持风格统一 | 所有Card相同阴影 |
| 可预测 | 符合用户习惯 | 点击查看详情 |
| 可撤销 | 允许误操作恢复 | 撤销删除操作 |
交互性能优化
// ✅ 使用const构造函数
const Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('静态内容'),
),
)
// ✅ 避免在build中创建新对象
class MyCard extends StatelessWidget {
final String title;
const MyCard({super.key, required this.title});
Widget build(BuildContext context) {
return Card(
child: Text(title),
);
}
}
// ❌ 避免不必要的重建
class BadCard extends StatefulWidget {
const BadCard({super.key});
State<BadCard> createState() => _BadCardState();
}
class _BadCardState extends State<BadCard> {
// ❌ 避免在build中创建新Widget
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: () {
// ❌ 避免在build中定义函数
print('tapped');
},
child: const Text('Click'),
),
);
}
}
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)