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

状态变量定义

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

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

练习统计

显示练习的统计数据:

Widget _buildStatistics() {
  return Container(
    margin: EdgeInsets.all(16.w),
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      gradient: const LinearGradient(
        colors: [Color(0xFF00897B), Color(0xFF4DB6AC)],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildStatItem('已练习', '$_currentIndex', Icons.check_circle),
        _buildStatItem('正确率', '${_calculateAccuracy()}%', Icons.trending_up),
        _buildStatItem('剩余', '${_cards.length - _currentIndex}', Icons.pending),
      ],
    ),
  );
}

Widget _buildStatItem(String label, String value, IconData icon) {
  return Column(
    children: [
      Icon(icon, color: Colors.white, size: 24.sp),
      SizedBox(height: 4.h),
      Text(
        value,
        style: TextStyle(
          fontSize: 20.sp,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      ),
      Text(
        label,
        style: TextStyle(
          fontSize: 12.sp,
          color: Colors.white.withOpacity(0.9),
        ),
      ),
    ],
  );
}

int _calculateAccuracy() {
  if (_correctCount + _wrongCount == 0) return 0;
  return ((_correctCount / (_correctCount + _wrongCount)) * 100).toInt();
}

统计卡片使用渐变背景,显示已练习数量、正确率和剩余数量三个关键指标。白色图标和文字在渐变背景上清晰可见。

错题回顾

支持查看和复习错题:

List<FlashCard> _wrongCards = [];

Widget _buildWrongCardsButton() {
  if (_wrongCards.isEmpty) return const SizedBox.shrink();
  
  return Container(
    margin: EdgeInsets.all(16.w),
    child: ElevatedButton.icon(
      onPressed: _reviewWrongCards,
      icon: const Icon(Icons.replay),
      label: Text('复习错题 (${_wrongCards.length})'),
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.orange,
        minimumSize: Size(double.infinity, 48.h),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12.r),
        ),
      ),
    ),
  );
}

void _reviewWrongCards() {
  setState(() {
    _cards = List.from(_wrongCards);
    _wrongCards.clear();
    _currentIndex = 0;
    _correctCount = 0;
    _wrongCount = 0;
    _showAnswer = false;
  });
  
  Get.snackbar(
    '开始复习',
    '共${_cards.length}道错题',
    snackPosition: SnackPosition.BOTTOM,
  );
}

错题回顾按钮显示错题数量,点击后重新开始练习这些错题。这个功能帮助用户针对性地复习薄弱环节。

学习建议

根据练习情况提供学习建议:

Widget _buildSuggestions() {
  final accuracy = _calculateAccuracy();
  String suggestion;
  Color color;
  IconData icon;
  
  if (accuracy >= 80) {
    suggestion = '太棒了!掌握得很好,可以学习新内容了。';
    color = Colors.green;
    icon = Icons.sentiment_very_satisfied;
  } else if (accuracy >= 60) {
    suggestion = '不错!继续加油,多练习几遍会更好。';
    color = Colors.orange;
    icon = Icons.sentiment_satisfied;
  } else {
    suggestion = '需要加强练习,建议重新学习相关课程。';
    color = Colors.red;
    icon = Icons.sentiment_dissatisfied;
  }
  
  return Container(
    margin: EdgeInsets.all(16.w),
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: color.withOpacity(0.1),
      borderRadius: BorderRadius.circular(12.r),
      border: Border.all(color: color.withOpacity(0.3)),
    ),
    child: Row(
      children: [
        Icon(icon, color: color, size: 32.sp),
        SizedBox(width: 12.w),
        Expanded(
          child: Text(
            suggestion,
            style: TextStyle(fontSize: 14.sp, color: color),
          ),
        ),
      ],
    ),
  );
}

学习建议根据正确率给出不同的反馈和建议。使用不同颜色和表情图标表达不同的评价等级。

练习记录

保存练习历史记录:

class PracticeRecord {
  final DateTime date;
  final int totalCards;
  final int correctCount;
  final int wrongCount;
  final int accuracy;

  PracticeRecord({
    required this.date,
    required this.totalCards,
    required this.correctCount,
    required this.wrongCount,
    required this.accuracy,
  });
}

List<PracticeRecord> _records = [];

void _savePracticeRecord() {
  final record = PracticeRecord(
    date: DateTime.now(),
    totalCards: _cards.length,
    correctCount: _correctCount,
    wrongCount: _wrongCount,
    accuracy: _calculateAccuracy(),
  );
  
  setState(() => _records.add(record));
  
  // 保存到本地存储
  _saveToLocal(record);
}

Widget _buildRecordsButton() {
  return TextButton.icon(
    onPressed: _showRecords,
    icon: const Icon(Icons.history),
    label: const Text('练习记录'),
  );
}

void _showRecords() {
  showModalBottomSheet(
    context: context,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)),
    ),
    builder: (context) => Container(
      padding: EdgeInsets.all(16.w),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '练习记录',
            style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 16.h),
          if (_records.isEmpty)
            Center(
              child: Text(
                '暂无练习记录',
                style: TextStyle(fontSize: 14.sp, color: Colors.grey[500]),
              ),
            )
          else
            Expanded(
              child: ListView.builder(
                itemCount: _records.length,
                itemBuilder: (context, index) {
                  final record = _records[index];
                  return ListTile(
                    leading: CircleAvatar(
                      backgroundColor: const Color(0xFF00897B).withOpacity(0.1),
                      child: Text(
                        '${record.accuracy}%',
                        style: const TextStyle(
                          fontSize: 12,
                          color: Color(0xFF00897B),
                        ),
                      ),
                    ),
                    title: Text(
                      DateFormat('yyyy-MM-dd HH:mm').format(record.date),
                    ),
                    subtitle: Text(
                      '练习${record.totalCards}题 正确${record.correctCount}题',
                    ),
                  );
                },
              ),
            ),
        ],
      ),
    ),
  );
}

练习记录功能保存每次练习的详细数据,包括日期、题目数、正确数、错误数和正确率。用户可以查看历史记录,了解学习进度。

分享成绩

支持分享练习成绩:

Widget _buildShareButton() {
  return ElevatedButton.icon(
    onPressed: _shareResult,
    icon: const Icon(Icons.share),
    label: const Text('分享成绩'),
    style: ElevatedButton.styleFrom(
      backgroundColor: const Color(0xFF2196F3),
      minimumSize: Size(double.infinity, 48.h),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
    ),
  );
}

void _shareResult() {
  final text = '''
我在手语学习App完成了闪卡练习!

练习题数:${_cards.length}
正确题数:$_correctCount
正确率:${_calculateAccuracy()}%

一起来学习手语吧!
''';

  Share.share(text, subject: '我的练习成绩');
}

分享功能将练习成绩整理成文本,通过系统分享功能发送。这个功能可以激励用户坚持学习,也能吸引更多人使用应用。

练习模式选择

提供不同的练习模式:

enum PracticeMode {
  sequential, // 顺序练习
  random,     // 随机练习
  wrong,      // 错题练习
}

PracticeMode _mode = PracticeMode.sequential;

Widget _buildModeSelector() {
  return Container(
    margin: EdgeInsets.all(16.w),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '练习模式',
          style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 12.h),
        Wrap(
          spacing: 8.w,
          children: [
            ChoiceChip(
              label: const Text('顺序练习'),
              selected: _mode == PracticeMode.sequential,
              onSelected: (selected) {
                if (selected) {
                  setState(() => _mode = PracticeMode.sequential);
                  _resetPractice();
                }
              },
            ),
            ChoiceChip(
              label: const Text('随机练习'),
              selected: _mode == PracticeMode.random,
              onSelected: (selected) {
                if (selected) {
                  setState(() => _mode = PracticeMode.random);
                  _shuffleCards();
                }
              },
            ),
            ChoiceChip(
              label: const Text('错题练习'),
              selected: _mode == PracticeMode.wrong,
              onSelected: (selected) {
                if (selected && _wrongCards.isNotEmpty) {
                  setState(() => _mode = PracticeMode.wrong);
                  _reviewWrongCards();
                }
              },
            ),
          ],
        ),
      ],
    ),
  );
}

void _shuffleCards() {
  setState(() {
    _cards.shuffle();
    _currentIndex = 0;
    _showAnswer = false;
  });
}

练习模式选择器提供三种模式:顺序练习、随机练习和错题练习。用户可以根据需要选择不同的练习方式。

总结

闪卡练习页面通过丰富的功能模块,为用户提供了高效的学习工具。从基础的翻卡练习到统计分析,从错题回顾到学习建议,每个功能都旨在提升学习效果。练习记录和分享功能增加了应用的社交属性,激励用户坚持学习。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐