Flutter开发:如何同时翻转两张FlipCard并解决状态同步问题
解决方案:Flutter卡片匹配游戏的翻转动画与状态控制
方案一:修复FlipCard包的使用问题
核心问题是手动维护状态导致的不一致,以及未正确处理动画期间的点击锁定。以下是修正后的实现:
1. 定义核心变量
List<FlipCardController> _cardControllers = List.generate(12, (_) => FlipCardController()); bool _isProcessing = false; // 动画处理中禁止点击 List<int> _pickedCards = []; List<String> _cardValues = ['A','A','B','B','C','C','D','D','E','E','F','F']; // 卡片匹配值
2. 构建卡片网格
GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemCount: 12, itemBuilder: (context, index) { return FlipCard( controller: _cardControllers[index], front: const Card(child: Center(child: Text('背面'))), back: Card(child: Center(child: Text(_cardValues[index]))), onTap: _isProcessing ? null : () => _onCardTap(index), // 处理中禁用点击 ); }, )
3. 卡片点击与匹配校验逻辑
void _onCardTap(int index) { // 只允许选择未翻转的卡片,且最多选2张 if (_pickedCards.length < 2 && _cardControllers[index].state!.isFront) { _pickedCards.add(index); _cardControllers[index].toggleCard(); // 选满2张开始校验 if (_pickedCards.length == 2) { _checkMatch(); } } } void _checkMatch() async { _isProcessing = true; setState(() {}); // 等待翻转动画完成(默认300ms,留50ms缓冲) await Future.delayed(const Duration(milliseconds: 350)); bool isMatch = _cardValues[_pickedCards[0]] == _cardValues[_pickedCards[1]]; if (!isMatch) { // 不匹配时延迟1秒给用户查看,再翻回 await Future.delayed(const Duration(milliseconds: 1000)); _cardControllers[_pickedCards[0]].toggleCard(); _cardControllers[_pickedCards[1]].toggleCard(); } // 重置状态 _pickedCards.clear(); _isProcessing = false; setState(() {}); }
关键改进点:
- 用
_isProcessing全局锁禁止动画期间的点击,避免并发操作 - 不再手动维护
isFront状态,直接通过FlipCardController的state获取真实状态 - 等待动画完成后再执行校验和翻回操作,确保状态同步
方案二:自定义翻转动画(不依赖FlipCard包)
如果不想用第三方包,可以自己实现可控的翻转动画,避免依赖问题:
1. 自定义翻转卡片Widget
class FlipCardWidget extends StatefulWidget { final String frontText; final String backText; final bool isFlipped; final VoidCallback? onTap; const FlipCardWidget({ super.key, required this.frontText, required this.backText, required this.isFlipped, this.onTap, }); @override State<FlipCardWidget> createState() => _FlipCardWidgetState(); } class _FlipCardWidgetState extends State<FlipCardWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _animation = Tween<double>(begin: 0, end: 1).animate(_controller); // 初始化时同步翻转状态 if (widget.isFlipped) _controller.value = 1; } @override void didUpdateWidget(covariant FlipCardWidget oldWidget) { super.didUpdateWidget(oldWidget); // 状态变化时触发动画 if (widget.isFlipped != oldWidget.isFlipped) { widget.isFlipped ? _controller.forward() : _controller.reverse(); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: AnimatedBuilder( animation: _animation, builder: (context, child) { final isFront = _animation.value < 0.5; return Transform( transform: Matrix4.identity() ..setEntry(3, 2, 0.001) // 添加透视效果 ..rotateY(_animation.value * pi), alignment: Alignment.center, child: isFront ? Card(child: Center(child: Text(widget.frontText))) : Transform( // 背面需要反转,避免文字倒置 transform: Matrix4.identity()..rotateY(pi), alignment: Alignment.center, child: Card(child: Center(child: Text(widget.backText))), ), ); }, ), ); } }
2. 主页面使用自定义卡片
List<bool> _isFlipped = List.generate(12, (_) => false); bool _isProcessing = false; List<int> _pickedCards = []; List<String> _cardValues = ['A','A','B','B','C','C','D','D','E','E','F','F']; GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemCount: 12, itemBuilder: (context, index) { return FlipCardWidget( frontText: '背面', backText: _cardValues[index], isFlipped: _isFlipped[index], onTap: _isProcessing ? null : () => _onCardTap(index), ); }, )
3. 点击与校验逻辑
void _onCardTap(int index) { if (_pickedCards.length < 2 && !_isFlipped[index]) { setState(() => _isFlipped[index] = true); _pickedCards.add(index); if (_pickedCards.length == 2) { _checkMatch(); } } } void _checkMatch() async { _isProcessing = true; setState(() {}); await Future.delayed(const Duration(milliseconds: 300)); // 等待动画完成 bool isMatch = _cardValues[_pickedCards[0]] == _cardValues[_pickedCards[1]]; if (!isMatch) { await Future.delayed(const Duration(milliseconds: 1000)); setState(() { _isFlipped[_pickedCards[0]] = false; _isFlipped[_pickedCards[1]] = false; }); } _pickedCards.clear(); _isProcessing = false; setState(() {}); }
优势:完全可控的动画逻辑,避免第三方包的状态同步问题,自定义程度更高。
内容的提问来源于stack exchange,提问作者Giovanni Caciuppo
相关产品推荐
相关产品推荐

