手势组件详解
手势交互是移动应用的核心功能。Flutter 提供了丰富的手势识别组件,可以轻松处理用户的触摸交互。本教程将详细介绍各种手势组件。
重要提示:Flutter 3.28+ 进一步优化了手势识别的性能和响应速度。
1. GestureDetector 基础手势
GestureDetector 是最常用的手势识别组件,可以识别多种手势。
📐 GestureDetector 手势类型
👆
onTap
点击
👆👆
onDoubleTap
双击
⏱️
onLongPress
长按
↔️
onHorizontalDrag
水平拖动
↕️
onVerticalDrag
垂直拖动
✌️
onScale
缩放
class GestureDetectorExample extends StatefulWidget {
const GestureDetectorExample({super.key});
@override
State createState() =>
_GestureDetectorExampleState();
}
class _GestureDetectorExampleState extends State {
String _gesture = '等待手势';
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
// 点击手势
onTap: () {
setState(() {
_gesture = '点击';
});
},
onDoubleTap: () {
setState(() {
_gesture = '双击';
});
},
onLongPress: () {
setState(() {
_gesture = '长按';
});
},
// 滑动手势
onHorizontalDragStart: (details) {
setState(() {
_gesture = '水平滑动开始';
});
},
onHorizontalDragUpdate: (details) {
setState(() {
_gesture = '水平滑动: ${details.globalPosition.dx}';
});
},
onHorizontalDragEnd: (details) {
setState(() {
_gesture = '水平滑动结束';
});
},
// 缩放手势
onScaleStart: (details) {
setState(() {
_gesture = '缩放开始';
});
},
onScaleUpdate: (details) {
setState(() {
_gesture = '缩放: ${details.scale}';
});
},
onScaleEnd: (details) {
setState(() {
_gesture = '缩放结束';
});
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
_gesture,
style: const TextStyle(color: Colors.white),
),
),
),
),
);
}
}
2. InkWell 涟漪效果
InkWell 提供了 Material Design 的涟漪效果。
📐 InkWell 涟漪效果
点击前
点击我
点击时 (涟漪效果)
Material Design 规范的点击反馈效果
InkWell(
onTap: () {
print('点击了卡片');
},
onLongPress: () {
print('长按了卡片');
},
splashColor: Colors.blue.withOpacity(0.3),
highlightColor: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
child: Container(
width: 200,
height: 100,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'点击我',
style: TextStyle(color: Colors.white),
),
),
),
)
3. Dismissible 滑动删除
Dismissible 用于实现滑动删除功能。
📐 Dismissible 滑动删除效果
🗑️
← 向左滑动删除
← 滑动方向可配置 (startToEnd / endToStart)
class DismissibleExample extends StatelessWidget {
const DismissibleExample({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Dismissible(
key: Key('item_$index'),
onDismissed: (direction) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('删除了项目 $index')),
);
},
direction: DismissDirection.endToStart,
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.green,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 20),
child: const Icon(Icons.archive, color: Colors.white),
),
child: ListTile(
title: Text('项目 $index'),
subtitle: Text('这是项目 $index 的描述'),
),
);
},
);
}
}
4. Draggable 拖拽
Draggable 用于实现可拖拽的组件。
📐 Draggable 拖拽效果
静态状态
A
B
C
拖拽状态
A
B
C
A
拖拽时显示 feedback,原位置显示 childWhenDragging(半透明)
class DraggableExample extends StatefulWidget {
const DraggableExample({super.key});
@override
State createState() => _DraggableExampleState();
}
class _DraggableExampleState extends State {
List _items = ['A', 'B', 'C', 'D', 'E'];
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('拖拽下面的字母'),
const SizedBox(height: 20),
Wrap(
spacing: 10,
children: _items.map((item) {
return Draggable(
data: item,
child: DraggableItem(text: item),
feedback: DraggableItem(
text: item,
isDragging: true,
),
childWhenDragging: DraggableItem(
text: item,
isDragging: false,
opacity: 0.3,
),
);
}).toList(),
),
],
);
}
}
class DraggableItem extends StatelessWidget {
final String text;
final bool isDragging;
final double? opacity;
const DraggableItem({
super.key,
required this.text,
this.isDragging = false,
this.opacity,
});
@override
Widget build(BuildContext context) {
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: isDragging ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
text,
style: TextStyle(
color: isDragging ? Colors.white : Colors.black,
fontSize: 24,
),
),
),
);
}
}
5. DragTarget 拖放目标
DragTarget 用于接收拖拽的组件。
📐 DragTarget 拖放效果
空闲状态
拖拽
拖放目标
已接收: 0 项
已接收: 0 项
悬停状态
拖拽
拖放目标
已接收: 0 项
已接收: 0 项
拖拽
悬停时高亮显示(绿色背景+绿色边框),释放时触发 onAccept
class DragTargetExample extends StatefulWidget {
const DragTargetExample({super.key});
@override
State createState() => _DragTargetExampleState();
}
class _DragTargetExampleState extends State {
List _acceptedItems = [];
@override
Widget build(BuildContext context) {
return Column(
children: [
Draggable(
data: '可拖拽项',
child: const DraggableItem(text: '拖拽我'),
feedback: const DraggableItem(text: '拖拽我', isDragging: true),
),
const SizedBox(height: 40),
DragTarget(
onAcceptWithDetails: (details) {
setState(() {
_acceptedItems.add(details.data);
});
},
builder: (context, candidateData, rejectedData) {
return Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: candidateData.isNotEmpty
? Colors.green.withOpacity(0.5)
: Colors.grey[300],
border: Border.all(
color: candidateData.isNotEmpty ? Colors.green : Colors.grey,
width: 2,
),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'拖放目标\n已接收: ${_acceptedItems.length} 项',
textAlign: TextAlign.center,
),
),
);
},
),
],
);
}
}
6. PageView 页面滑动
PageView 用于实现可滑动的页面视图。
📐 PageView 页面滑动效果
页面 1 (当前)
页面 2 (当前)
页面 3 (当前)
左右滑动切换页面,底部指示器显示当前页面
class PageViewExample extends StatefulWidget {
const PageViewExample({super.key});
@override
State createState() => _PageViewExampleState();
}
class _PageViewExampleState extends State {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 300,
child: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
itemCount: 5,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(
child: Text(
'页面 ${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 32,
),
),
),
);
},
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
5,
(index) => Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentPage == index ? Colors.blue : Colors.grey,
),
),
),
),
],
);
}
}
7. RefreshIndicator 下拉刷新
RefreshIndicator 用于实现下拉刷新功能。
📐 RefreshIndicator 下拉刷新效果
正常状态
下拉刷新状态
下拉列表触发刷新,显示旋转动画和刷新提示
class RefreshIndicatorExample extends StatefulWidget {
const RefreshIndicatorExample({super.key});
@override
State createState() =>
_RefreshIndicatorExampleState();
}
class _RefreshIndicatorExampleState extends State {
List _items = List.generate(10, (index) => '项目 $index');
Future _refresh() async {
await Future.delayed(const Duration(seconds: 2));
setState(() {
_items = List.generate(10, (index) => '刷新项目 $index');
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refresh,
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_items[index]),
);
},
),
);
}
}
⏱️ 手势识别时序图
用户
GestureDetector
Widget
📊 手势类型对比
| 手势 | 回调方法 | 触发条件 | 使用场景 |
|---|---|---|---|
| 点击 | onTap | 快速点击 | 按钮、卡片 |
| 双击 | onDoubleTap | 快速双击 | 点赞、放大 |
| 长按 | onLongPress | 长按500ms | 菜单、选择 |
| 拖拽 | onPanUpdate | 手指移动 | 滑动、移动 |
| 缩放 | onScaleUpdate | 双指缩放 | 图片缩放 |
手势最佳实践
- 优先使用 InkWell 而不是 GestureDetector 来实现点击效果
- 为手势操作提供视觉反馈(涟漪效果、高亮等)
- 合理设置手势的识别区域,避免误触
- 使用 Dismissible 时提供明确的删除提示
- 拖拽操作应该有清晰的视觉指示
- 为长按操作提供上下文菜单
- 测试手势在不同设备上的表现
提示:使用 RawGestureDetector 可以实现更复杂的手势识别。
下一步:掌握手势组件后,继续学习导航组件。