在这里插入图片描述

闪卡是一种高效的记忆方式,正面显示词汇,点击翻转显示手语动作说明。用户可以左右滑动切换卡片,标记掌握程度来强化记忆效果。

状态变量定义

闪卡需要追踪当前索引和翻转状态:

class FlashcardScreen extends StatefulWidget {
  const FlashcardScreen({super.key});

  
  State<FlashcardScreen> createState() => _FlashcardScreenState();
}

class _FlashcardScreenState extends State<FlashcardScreen> {
  int _currentIndex = 0;
  bool _showAnswer = false;

_currentIndex是当前卡片索引,_showAnswer控制是否显示答案面。这两个状态会随用户操作频繁变化。

闪卡数据准备

准备一组手语词汇卡片:

  final List<Map<String, String>> _cards = [
    {
      'word': '你好',
      'description': '右手握拳,拇指伸出,从额头向前挥动'
    },
    {
      'word': '谢谢',
      'description': '右手平伸,手心向上,从下巴处向前推出'
    },
    {
      'word': '对不起',
      'description': '右手握拳放在胸前,顺时针画圈'
    },
    {
      'word': '再见',
      'description': '手掌向外,左右摆动'
    },

每张卡片包含词汇和动作描述,实际项目中还可以加入图片或视频链接。

    {
      'word': '我爱你',
      'description': '同时伸出拇指、食指和小指'
    },
    {
      'word': '帮助',
      'description': '一只手托住另一只手的拳头向上推'
    },
    {
      'word': '吃饭',
      'description': '手指并拢,做往嘴里送食物的动作'
    },
    {
      'word': '喝水',
      'description': '手做握杯状,向嘴边倾斜'
    },
  ];

涵盖基础问候、情感表达、日常用语等多个类别的词汇。

页面整体结构

AppBar显示进度,body包含卡片和控制按钮:

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('闪卡练习'),
        actions: [
          Center(
            child: Padding(
              padding: EdgeInsets.only(right: 16.w),
              child: Text(
                '${_currentIndex + 1}/${_cards.length}',
                style: TextStyle(fontSize: 16.sp),
              ),
            ),
          ),
        ],
      ),

AppBar右侧显示当前是第几张卡片,让用户了解练习进度。

手势交互区域

点击翻转,滑动切换:

      body: Column(
        children: [
          Expanded(
            child: GestureDetector(
              onTap: () => setState(() => _showAnswer = !_showAnswer),
              onHorizontalDragEnd: (details) {
                if (details.primaryVelocity! < 0) {
                  _nextCard();
                } else if (details.primaryVelocity! > 0) {
                  _previousCard();
                }
              },

onTap切换正反面,onHorizontalDragEnd检测滑动方向。primaryVelocity小于0是左滑下一张,大于0是右滑上一张。

              child: Container(
                margin: EdgeInsets.all(20.w),
                child: AnimatedSwitcher(
                  duration: const Duration(milliseconds: 300),
                  child: _buildCard(),
                ),
              ),
            ),
          ),
          _buildControls(),
          SizedBox(height: 20.h),
        ],
      ),
    );
  }

AnimatedSwitcher在卡片切换时自动添加过渡动画,300毫秒的时长比较自然流畅。

卡片组件构建

根据状态显示正面或反面:

  Widget _buildCard() {
    final card = _cards[_currentIndex];
    return Card(
      key: ValueKey('$_currentIndex-$_showAnswer'),
      elevation: 8,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(20.r),
      ),
      child: Container(
        width: double.infinity,
        padding: EdgeInsets.all(32.w),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (!_showAnswer) ...[

key包含索引和状态,确保AnimatedSwitcher能正确识别变化触发动画。elevation: 8给卡片添加阴影效果。

卡片正面内容

显示词汇和提示:

              Icon(
                Icons.sign_language,
                size: 100.sp,
                color: const Color(0xFF00897B),
              ),
              SizedBox(height: 32.h),
              Text(
                card['word']!,
                style: TextStyle(
                  fontSize: 48.sp,
                  fontWeight: FontWeight.bold,
                ),
              ),
              SizedBox(height: 16.h),
              Text(
                '点击查看手语动作',
                style: TextStyle(
                  fontSize: 14.sp,
                  color: Colors.grey
                ),
              ),
            ]

正面显示大号词汇和手语图标,底部提示用户点击查看答案。大字体设计让词汇更醒目。

卡片反面内容

显示动作描述:

            else ...[
              Container(
                width: 150.w,
                height: 150.w,
                decoration: BoxDecoration(
                  color: const Color(0xFF00897B).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(75.r),
                ),
                child: Icon(
                  Icons.sign_language,
                  size: 80.sp,
                  color: const Color(0xFF00897B),
                ),
              ),

反面图标放在圆形背景中,视觉上更柔和。

              SizedBox(height: 32.h),
              Text(
                card['word']!,
                style: TextStyle(
                  fontSize: 32.sp,
                  fontWeight: FontWeight.bold,
                ),
              ),
              SizedBox(height: 16.h),
              Container(
                padding: EdgeInsets.all(16.w),
                decoration: BoxDecoration(
                  color: Colors.grey[100],
                  borderRadius: BorderRadius.circular(12.r),
                ),
                child: Text(
                  card['description']!,
                  style: TextStyle(fontSize: 16.sp, height: 1.5),
                  textAlign: TextAlign.center,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

词汇字号略小,重点是下方的动作描述。灰色背景让描述区域更突出。height: 1.5增加行高提升可读性。

控制区域构建

底部显示进度指示器和操作按钮:

  Widget _buildControls() {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 20.w),
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(_cards.length, (index) {
              return Container(
                width: 8.w,
                height: 8.w,
                margin: EdgeInsets.symmetric(horizontal: 4.w),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: index == _currentIndex
                      ? const Color(0xFF00897B)
                      : Colors.grey[300],
                ),
              );
            }),
          ),

小圆点指示器,当前卡片对应的圆点用主题色高亮。List.generate根据卡片数量生成圆点。

掌握程度按钮

两个按钮标记掌握情况:

          SizedBox(height: 20.h),
          Row(
            children: [
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: () => _markCard(false),
                  icon: const Icon(Icons.close, color: Colors.white),
                  label: const Text(
                    '不熟悉',
                    style: TextStyle(color: Colors.white)
                  ),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red[400],
                    padding: EdgeInsets.symmetric(vertical: 12.h),
                  ),
                ),
              ),

红色"不熟悉"按钮,点击后标记这张卡片需要继续练习。

              SizedBox(width: 12.w),
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: () => _markCard(true),
                  icon: const Icon(Icons.check, color: Colors.white),
                  label: const Text(
                    '已掌握',
                    style: TextStyle(color: Colors.white)
                  ),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green[400],
                    padding: EdgeInsets.symmetric(vertical: 12.h),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

绿色"已掌握"按钮,颜色对比鲜明。点击后自动切换到下一张卡片。

切换卡片逻辑

前后切换的方法:

  void _nextCard() {
    if (_currentIndex < _cards.length - 1) {
      setState(() {
        _currentIndex++;
        _showAnswer = false;
      });
    }
  }

  void _previousCard() {
    if (_currentIndex > 0) {
      setState(() {
        _currentIndex--;
        _showAnswer = false;
      });
    }
  }

切换时重置_showAnswer为false,确保新卡片显示正面。边界检查防止索引越界。

标记卡片逻辑

标记掌握程度并切换:

  void _markCard(bool mastered) {
    if (_currentIndex < _cards.length - 1) {
      _nextCard();
    } else {
      _showCompletionDialog();
    }
  }

如果不是最后一张就切换到下一张,最后一张标记后弹出完成对话框。

完成对话框

练习完成后的反馈:

  void _showCompletionDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('练习完成!'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              Icons.celebration,
              size: 64.sp,
              color: Colors.amber
            ),
            SizedBox(height: 16.h),
            Text('你已完成 ${_cards.length} 张闪卡的练习'),
          ],
        ),

庆祝图标配合完成提示,mainAxisSize: MainAxisSize.min让对话框高度自适应内容。

        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              Navigator.pop(context);
            },
            child: const Text('返回'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
              setState(() {
                _currentIndex = 0;
                _showAnswer = false;
              });
            },
            child: const Text('再练一次'),
          ),
        ],
      ),
    );
  }

两个按钮:返回上一页或重新开始。重新开始时重置索引和状态从头练习。

小结

闪卡练习的核心是点击翻转和滑动切换的交互设计。AnimatedSwitcher提供平滑的过渡动画,掌握程度按钮帮助用户自我评估学习效果。这种主动回忆的学习方式比被动阅读更有效,能加深记忆。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐