如何在Flutter中利用FlipCardController按条件翻转指定卡片?
解决方案:实现记忆游戏卡片自动翻回逻辑
核心问题分析
你当前的代码存在两个关键问题:
- 每个
PlaycardGridItem的FlipCardController是局部独立的,当翻转卡片数量达到2时,所有卡片都会触发toggleCard,而非仅选中的两张。 - 缺少延迟判断匹配的逻辑,没有区分"匹配成功保持翻转"和"匹配失败自动翻回"的场景。
步骤1:完善Riverpod状态管理
首先修改你的cards_provider.dart,让状态管理同时处理翻转、匹配和延迟逻辑:
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:memory/models/playcard.dart'; final cardProvider = StateNotifierProvider<CardNotifier, CardState>((ref) { return CardNotifier(); }); class CardState { final List<Playcard> flippedCards; final List<Playcard> matchedCards; final bool isProcessing; // 防止翻回过程中重复点击 CardState({ required this.flippedCards, required this.matchedCards, required this.isProcessing, }); CardState.initial() : flippedCards = [], matchedCards = [], isProcessing = false; } class CardNotifier extends StateNotifier<CardState> { CardNotifier() : super(CardState.initial()); void flipCard(Playcard card) async { // 拦截无效点击:处理中、已匹配、已选满两张 if (state.isProcessing || state.matchedCards.contains(card) || state.flippedCards.length >= 2) { return; } // 取消已选中卡片的翻转 if (state.flippedCards.contains(card)) { state = state.copyWith( flippedCards: List.from(state.flippedCards)..remove(card), ); return; } // 添加新选中卡片 final newFlipped = List.from(state.flippedCards)..add(card); state = state.copyWith(flippedCards: newFlipped); // 选中两张后判断匹配 if (newFlipped.length == 2) { state = state.copyWith(isProcessing: true); await Future.delayed(const Duration(seconds: 1)); // 延迟1秒让用户看清内容 final first = newFlipped[0]; final second = newFlipped[1]; if (first.back == second.back) { // 假设back是匹配标识(如相同图片路径) // 匹配成功:加入已匹配列表 final newMatched = List.from(state.matchedCards) ..add(first) ..add(second); state = state.copyWith( flippedCards: [], matchedCards: newMatched, isProcessing: false, ); } else { // 匹配失败:清空翻转列表,触发卡片翻回 state = state.copyWith( flippedCards: [], isProcessing: false, ); } } } } // 给CardState添加copyWith方法 extension CardStateCopy on CardState { CardState copyWith({ List<Playcard>? flippedCards, List<Playcard>? matchedCards, bool? isProcessing, }) { return CardState( flippedCards: flippedCards ?? this.flippedCards, matchedCards: matchedCards ?? this.matchedCards, isProcessing: isProcessing ?? this.isProcessing, ); } }
步骤2:修改PlaycardGridItem组件
改为使用ConsumerStatefulWidget,让每个卡片根据全局状态自动控制翻转:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:memory/models/playcard.dart'; import 'package:flip_card/flip_card.dart'; import 'package:memory/providers/cards_provider.dart'; import 'package:flip_card/flip_card_controller.dart'; class PlaycardGridItem extends ConsumerStatefulWidget { const PlaycardGridItem({super.key, required this.playcard}); final Playcard playcard; @override ConsumerState<PlaycardGridItem> createState() => _PlaycardGridItemState(); } class _PlaycardGridItemState extends ConsumerState<PlaycardGridItem> { late final FlipCardController _controller; @override void initState() { super.initState(); _controller = FlipCardController(); } @override void didUpdateWidget(covariant PlaycardGridItem oldWidget) { super.didUpdateWidget(oldWidget); _syncCardState(); } @override void didChangeDependencies() { super.didChangeDependencies(); _syncCardState(); } // 同步卡片翻转状态与全局状态 void _syncCardState() { final cardState = ref.watch(cardProvider); final shouldBeFlipped = cardState.flippedCards.contains(widget.playcard) || cardState.matchedCards.contains(widget.playcard); // 当前状态与预期不一致时触发翻转 if (_controller.state!.isFront != !shouldBeFlipped) { _controller.toggleCard(animated: true); } } @override Widget build(BuildContext context) { final cardState = ref.watch(cardProvider); return Material( child: InkWell( splashColor: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(16), onTap: cardState.isProcessing ? null : () { ref.read(cardProvider.notifier).flipCard(widget.playcard); }, child: FlipCard( controller: _controller, flipOnTouch: false, // 关闭默认点击翻转,由全局状态控制 front: Card( color: Colors.white, elevation: 10, child: Center(child: Text(widget.playcard.front)), ), back: Card( color: Colors.white, elevation: 10, child: Center( child: Image( image: AssetImage('assets/images/${widget.playcard.back}'), fit: BoxFit.contain, height: 200, width: double.infinity, ), ), ), ), ), ); } }
步骤3:简化BoardScreen
原BoardScreen无需大幅修改,移除无用的打印和方法即可:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:memory/data/nini_cards.dart'; import 'package:memory/models/playcard.dart'; import 'package:memory/widgets/playcard_grid_item.dart'; class BoardScreen extends ConsumerWidget { const BoardScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar(title: const Text('Pick your cards')), body: GridView( padding: const EdgeInsets.all(24), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 3 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20, ), children: [ for (final playcard in doubledCards) PlaycardGridItem(playcard: playcard) ], ), ); } }
关键改进点
- 全局状态统一控制:所有卡片的翻转状态由Riverpod集中管理,避免局部控制器导致的混乱。
- 延迟匹配逻辑:选中两张卡片后延迟1秒判断匹配,提升用户体验。
- 点击拦截:处理翻回过程中禁止重复点击,防止状态异常。
- 状态同步:卡片组件自动监听全局状态变化,自动调整翻转状态。
内容的提问来源于stack exchange,提问作者Gunnar Polte
相关产品推荐
相关产品推荐

