Flutter卡牌游戏开发:实现卡牌平滑动画移至弃牌堆
Flutter卡牌移动至弃牌堆的动画解决方案
问题1:AnimatedPositioned无动画的修复
AnimatedPositioned触发动画的核心条件:
- 父容器必须是
Stack组件 - 通过
setState修改left/top等位置参数,且前后参数值存在明确差异
示例代码:
class _CardState extends State<CardWidget> { double _cardLeft = 100; // 手牌初始X位置 double _cardTop = 400; // 手牌初始Y位置 final _discardLeft = 20; // 弃牌堆基准X位置 final _discardTop = 20; // 弃牌堆基准Y位置 void moveToDiscard() { setState(() { // 修改位置参数触发动画 _cardLeft = _discardLeft; _cardTop = _discardTop; }); } @override Widget build(BuildContext context) { return Stack( children: [ AnimatedPositioned( duration: const Duration(milliseconds: 500), left: _cardLeft, top: _cardTop, child: GestureDetector( onTap: moveToDiscard, child: Image.asset('assets/card.png', width: 100, height: 150), ), ), ], ); } }
问题2:SlideTransition的堆叠与动画平衡
要解决手牌重叠、弃牌堆分散的问题,需给弃牌堆每张卡牌设置递增的微小偏移,同时用SlideTransition控制从手牌位置到弃牌目标位置的动画。
完整实现示例:
import 'package:flutter/material.dart'; class CardGame extends StatefulWidget { @override _CardGameState createState() => _CardGameState(); } class _CardGameState extends State<CardGame> with SingleTickerProviderStateMixin { final List<CardData> _handCards = []; final List<CardData> _discardPile = []; late AnimationController _animController; @override void initState() { super.initState(); _animController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); // 初始化测试卡牌 _handCards.add(CardData( id: 'card_1', image: const AssetImage('assets/card.png'), startPos: const Offset(100, 400), )); } @override void dispose() { _animController.dispose(); super.dispose(); } void _moveCardToDiscard(CardData card) { setState(() { _handCards.remove(card); // 计算弃牌堆目标位置:基准位置 + 索引偏移(每张错开5px形成堆叠) final targetPos = Offset( 20 + _discardPile.length * 5, 20 + _discardPile.length * 5, ); card.targetPos = targetPos; _discardPile.add(card); }); // 重启动画 _animController.forward(from: 0); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 渲染手牌区(固定位置) ..._handCards.map((card) { return Positioned( left: card.startPos.dx, top: card.startPos.dy, child: GestureDetector( onTap: () => _moveCardToDiscard(card), child: Image(image: card.image, width: 100, height: 150), ), ); }), // 渲染弃牌堆(带移动动画) ..._discardPile.map((card) { final anim = Tween<Offset>( begin: card.startPos, end: card.targetPos, ).animate(_animController); return SlideTransition( position: anim, child: Image(image: card.image, width: 100, height: 150), ); }), ], ), ); } } // 卡牌数据模型 class CardData { final String id; final ImageProvider image; final Offset startPos; Offset? targetPos; CardData({ required this.id, required this.image, required this.startPos, }); }
关键说明
- 弃牌堆通过
_discardPile.length * 5设置递增偏移,既避免完全重叠,又不会分散排列 - SlideTransition使用
Tween<Offset>定义动画起止位置,配合AnimationController控制动画流程 - 手牌区用Positioned固定位置,点击后从手牌列表移除,加入弃牌堆并触发动画
内容的提问来源于stack exchange,提问作者Jhevi P.
相关产品推荐
相关产品推荐

