You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中利用FlipCardController按条件翻转指定卡片?

解决方案:实现记忆游戏卡片自动翻回逻辑

核心问题分析

你当前的代码存在两个关键问题:

  1. 每个PlaycardGridItem的FlipCardController是局部独立的,当翻转卡片数量达到2时,所有卡片都会触发toggleCard,而非仅选中的两张。
  2. 缺少延迟判断匹配的逻辑,没有区分"匹配成功保持翻转"和"匹配失败自动翻回"的场景。

步骤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)
        ],
      ),
    );
  }
}

关键改进点

  1. 全局状态统一控制:所有卡片的翻转状态由Riverpod集中管理,避免局部控制器导致的混乱。
  2. 延迟匹配逻辑:选中两张卡片后延迟1秒判断匹配,提升用户体验。
  3. 点击拦截:处理翻回过程中禁止重复点击,防止状态异常。
  4. 状态同步:卡片组件自动监听全局状态变化,自动调整翻转状态。

内容的提问来源于stack exchange,提问作者Gunnar Polte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 03:22:35