Flutter如何实现支持左右双向滑动的可滑动卡片堆叠效果
嘿,这个双向滑动的卡片堆叠效果在Flutter里实现起来并不复杂,我给你分享两种实用方案,你可以根据自己的需求选:
方案一:用第三方库快速落地
如果不想花太多时间写自定义逻辑,推荐用flutter_card_swiper这个专门做卡片滑动堆叠的库,它原生支持左右双向滑动,还自带了堆叠动画效果。
步骤1:添加依赖
在你的pubspec.yaml文件里加上这个依赖:
dependencies: flutter_card_swiper: ^1.0.1 # 记得替换成最新版本
然后执行flutter pub get拉取包。
步骤2:编写核心代码
这里给你一个简单的示例,直接实现可左右滑的卡片堆叠:
import 'package:flutter/material.dart'; import 'package:flutter_card_swiper/flutter_card_swiper.dart'; class SwipeCardStack extends StatefulWidget { const SwipeCardStack({super.key}); @override State<SwipeCardStack> createState() => _SwipeCardStackState(); } class _SwipeCardStackState extends State<SwipeCardStack> { final List<Widget> _cards = [ _buildCard('旅行卡片1', Colors.orangeAccent), _buildCard('美食卡片2', Colors.greenAccent), _buildCard('电影卡片3', Colors.blueAccent), _buildCard('读书卡片4', Colors.purpleAccent), ]; static Widget _buildCard(String text, Color color) { return Container( width: 320, height: 450, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow(blurRadius: 10, color: Colors.black26, offset: Offset(0, 6)) ], ), child: Center( child: Text(text, style: const TextStyle(fontSize: 28, color: Colors.white, fontWeight: FontWeight.bold)), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: CardSwiper( cards: _cards, onSwipeLeft: (index) => print('向左滑走了「${_cards[index].toString()}」'), onSwipeRight: (index) => print('向右滑走了「${_cards[index].toString()}」'), onEnd: () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('所有卡片都滑完啦~')) ), ), ), ); } }
这个示例里,你只需要传入卡片列表,就能自动获得堆叠布局和双向滑动功能,还能监听滑动方向和全部滑完的事件。
方案二:自定义实现(高度可控)
如果需要更个性化的动画或者交互逻辑——比如自定义堆叠偏移量、滑动时的旋转角度,那可以自己用GestureDetector+Stack来实现。
核心思路
- 用
Stack组件堆叠卡片,通过Transform让后面的卡片稍微缩小并偏移,形成堆叠层次感; - 用
GestureDetector监听拖拽事件,实时更新当前顶部卡片的位置和旋转角度; - 拖拽结束时判断滑动距离,超过阈值就移除顶部卡片,否则回弹到原位。
核心代码示例
import 'package:flutter/material.dart'; class CustomSwipeCardStack extends StatefulWidget { const CustomSwipeCardStack({super.key}); @override State<CustomSwipeCardStack> createState() => _CustomSwipeCardStackState(); } class _CustomSwipeCardStackState extends State<CustomSwipeCardStack> with SingleTickerProviderStateMixin { late AnimationController _reboundController; List<Color> _cardColors = [Colors.orangeAccent, Colors.greenAccent, Colors.blueAccent, Colors.purpleAccent]; double _offsetX = 0; double _rotation = 0; final double _swipeThreshold = 120; // 滑动触发移除的阈值 @override void initState() { super.initState(); _reboundController = AnimationController(vsync: this, duration: const Duration(milliseconds: 250)); } @override void dispose() { _reboundController.dispose(); super.dispose(); } void _onPanUpdate(DragUpdateDetails details) { setState(() { _offsetX += details.delta.dx; // 根据水平偏移量计算旋转角度,模拟自然拖拽的倾斜效果 _rotation = _offsetX / 120; }); } void _onPanEnd(DragEndDetails details) { if (_offsetX.abs() > _swipeThreshold) { // 滑动超过阈值,移除当前顶部卡片 setState(() { _cardColors.removeAt(0); _offsetX = 0; _rotation = 0; }); } else { // 回弹到初始位置 _reboundController.reset(); _reboundController.forward().then((_) { setState(() { _offsetX = 0; _rotation = 0; }); }); } } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Stack( alignment: Alignment.center, children: List.generate(_cardColors.length, (index) { // 后面的卡片缩小并向下偏移,营造堆叠层次感 final scale = 1 - (index * 0.06); final offsetY = index * 12.0; return index == 0 ? GestureDetector( onPanUpdate: _onPanUpdate, onPanEnd: _onPanEnd, child: Transform( transform: Matrix4.translationValues(_offsetX, 0, 0) ..rotateZ(_rotation) ..scale(scale), child: _buildCard(_cardColors[index]), ), ) : Transform( transform: Matrix4.translationValues(0, offsetY, 0) ..scale(scale), child: _buildCard(_cardColors[index]), ); }).reversed.toList(), // 反转列表,确保第一个卡片在最上层 ), ), ); } Widget _buildCard(Color color) { return Container( width: 320, height: 450, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow(blurRadius: 10, color: Colors.black26, offset: Offset(0, 6)) ], ), ); } }
这个自定义实现里,你可以调整_swipeThreshold(滑动阈值)、scale(卡片缩放比例)、offsetY(堆叠偏移量)来适配你的设计需求,滑动时的旋转效果也可以根据自己的喜好调整参数。
额外优化点
- 可以给卡片添加滑出动画,比如当卡片被滑走时,让它快速滑出屏幕并消失;
- 支持双击卡片、长按卡片等额外交互;
- 动态加载更多卡片,避免一次性加载太多导致性能问题。
内容的提问来源于stack exchange,提问作者Aanand S
相关产品推荐
相关产品推荐

