import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; class PhotoSwipeCard extends StatefulWidget { final List assets; final Function(int)? onIndexChanged; final Function(AssetEntity, bool)? onActionSelected; // true for keep, false for delete final Function()? onEnd; final bool enableVerticalDrag; final bool loop; final double threshold; const PhotoSwipeCard({ super.key, required this.assets, this.onIndexChanged, this.onActionSelected, this.onEnd, this.enableVerticalDrag = false, this.loop = false, this.threshold = 0.5, }); @override State createState() => _PhotoSwipeCardState(); } class _PhotoSwipeCardState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; late Animation _rotation; Offset _dragOffset = Offset.zero; int currentIndex = 0; // 新增:弧形动画相关计算 double get _rotationAngle => (_dragOffset.dx / MediaQuery.of(context).size.width) * 0.3; double get _verticalOffset => -math.sin(_rotationAngle * 2) * 100; double get _nextCardScale { final double dragPercent = _dragOffset.dx.abs() / (MediaQuery.of(context).size.width * 0.3); return 0.9 + (0.1 * math.min(1.0, dragPercent)); } @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _initializeAnimations(); } void _initializeAnimations() { _animation = Tween(begin: Offset.zero, end: Offset.zero) .animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, )); _rotation = Tween(begin: 0, end: 0).animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, )); } void _onPanStart(DragStartDetails details) { _controller.reset(); } void _onPanUpdate(DragUpdateDetails details) { setState(() { if (widget.enableVerticalDrag) { _dragOffset += details.delta; } else { _dragOffset += Offset(details.delta.dx, 0); } }); } void _onPanCancel() { _returnToCenter(); } void _returnToCenter() { setState(() { _dragOffset = Offset.zero; // 重置偏移量 }); } void _onPanEnd(DragEndDetails details) { final double threshold = widget.threshold * MediaQuery.of(context).size.width; final velocity = details.velocity.pixelsPerSecond.dx; final isFlick = velocity.abs() > 1000; if (_dragOffset.dx.abs() > threshold || isFlick) { final bool isKeepAction = _dragOffset.dx > 0; final bool isLastCard = currentIndex >= widget.assets.length - 1; // 添加弧形退出动画 final endRotation = _dragOffset.dx > 0 ? 0.3 : -0.3; final endVerticalOffset = -math.sin(endRotation * 2) * 100; _animation = Tween( begin: _dragOffset, end: Offset( _dragOffset.dx > 0 ? MediaQuery.of(context).size.width * 1.5 : -MediaQuery.of(context).size.width * 1.5, endVerticalOffset, ), ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, )); _controller.forward().then((_) { widget.onActionSelected ?.call(widget.assets[currentIndex], isKeepAction); setState(() { if (isLastCard) { if (widget.loop) { currentIndex = 0; _controller.addListener(_onLoopStarted); } else { currentIndex = widget.assets.length; widget.onEnd?.call(); } } else { currentIndex++; if (widget.loop && currentIndex == widget.assets.length) { currentIndex = 0; } } _dragOffset = Offset.zero; widget.onIndexChanged?.call(currentIndex); }); _controller.reset(); _initializeAnimations(); }); } else { _returnToCenter(); } } void _onLoopStarted() { if (currentIndex == 0) { widget.onEnd?.call(); _controller.removeListener(_onLoopStarted); } } @override Widget build(BuildContext context) { return Stack( children: [ // 下一张卡片 if (_hasNextCard()) Positioned.fill( child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.scale( scale: _nextCardScale, child: Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: AssetEntityImage( widget.assets[_getNextIndex()], width: 314.w, height: 392.h, fit: BoxFit.cover, ), ), ), ); }, ), ), // 当前卡片 if (currentIndex < widget.assets.length) AnimatedBuilder( animation: _controller, builder: (context, child) { final offset = _animation.value + _dragOffset; return Transform( transform: Matrix4.identity() ..translate(offset.dx, _verticalOffset + offset.dy) ..rotateZ(_rotationAngle) ..setEntry(3, 2, 0.001), // 添加透视效果 alignment: Alignment.center, child: Stack( children: [ Card( elevation: 8, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: GestureDetector( onPanStart: _onPanStart, onPanUpdate: _onPanUpdate, onPanEnd: _onPanEnd, onPanCancel: _onPanCancel, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Stack( children: [ AssetEntityImage( widget.assets[currentIndex], width: 314.w, height: 392.h, fit: BoxFit.cover, ), // 添加操作提示UI if (_dragOffset.dx != 0) Positioned( top: 20, right: _dragOffset.dx < 0 ? 20 : null, left: _dragOffset.dx > 0 ? 20 : null, child: Container( padding: EdgeInsets.symmetric( horizontal: 20.w, vertical: 10.h), decoration: BoxDecoration( color: _dragOffset.dx > 0 ? Colors.blue.withOpacity(0.8) : Colors.red.withOpacity(0.8), borderRadius: BorderRadius.circular(20), ), child: Text( _dragOffset.dx > 0 ? 'Keep' : 'Delete', style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ), ], ), ); }, ), ], ); } bool _hasNextCard() { if (widget.loop) { return widget.assets.length > 1; } return currentIndex < widget.assets.length - 1; } int _getNextIndex() { if (widget.loop) { return (currentIndex + 1) % widget.assets.length; } return currentIndex + 1; } @override void dispose() { _controller.dispose(); super.dispose(); } }