Flutter 框架跨平台鸿蒙开发 - 免费英语口语评测:AI智能发音纠正

Flutter免费英语口语评测:AI智能发音纠正

项目简介

免费英语口语评测是一款专为英语学习者打造的Flutter应用,提供发音录入、智能评分、纠正建议和学习进度追踪功能。通过模拟AI评测系统,帮助用户提高英语口语水平,实现随时随地练习口语。
运行效果图



核心功能

  • 句子练习:20个精选英语句子
  • 5大分类:日常对话、商务英语、旅游英语、学术英语、面试英语
  • 3个难度:初级、中级、高级
  • 录音功能:模拟语音录入
  • 智能评分:发音、流畅度、准确度三维评分
  • 纠正建议:针对性改进建议
  • 练习记录:完整的练习历史
  • 进度统计:学习数据可视化
  • 成绩分布:优秀、良好、及格统计
  • 学习建议:个性化学习指导

技术特点

  • Material Design 3设计风格
  • NavigationBar底部导航
  • 三页面架构(练习、记录、进度)
  • ChoiceChip分类筛选
  • 录音状态动画
  • 评分结果可视化
  • LinearProgressIndicator进度展示
  • 响应式卡片布局
  • 模拟AI评测
  • 无需额外依赖包

核心代码实现

1. 句子数据模型

dart 复制代码
class Sentence {
  final String id;              // 句子ID
  final String english;         // 英文句子
  final String chinese;         // 中文翻译
  final String category;        // 分类
  final String level;           // 难度级别
  final List<String> keywords;  // 关键词
  final String phonetic;        // 音标

  Sentence({
    required this.id,
    required this.english,
    required this.chinese,
    required this.category,
    required this.level,
    required this.keywords,
    required this.phonetic,
  });

  // 计算属性:难度颜色
  Color get levelColor {
    switch (level) {
      case '初级': return Colors.green;
      case '中级': return Colors.orange;
      case '高级': return Colors.red;
      default: return Colors.grey;
    }
  }

  // 计算属性:分类图标
  IconData get categoryIcon {
    switch (category) {
      case '日常对话': return Icons.chat;
      case '商务英语': return Icons.business;
      case '旅游英语': return Icons.flight;
      case '学术英语': return Icons.school;
      case '面试英语': return Icons.work;
      default: return Icons.language;
    }
  }
}

模型字段说明:

字段 类型 说明
id String 唯一标识符
english String 英文句子
chinese String 中文翻译
category String 分类(5种)
level String 难度级别
keywords List 关键词列表
phonetic String 音标

计算属性:

  • levelColor:根据难度级别返回对应颜色
  • categoryIcon:根据分类返回对应图标

分类与图标映射:

分类 图标 说明
日常对话 chat 日常交流用语
商务英语 business 商务场景用语
旅游英语 flight 旅游场景用语
学术英语 school 学术场景用语
面试英语 work 面试场景用语

难度级别与颜色:

级别 颜色 说明
初级 绿色 简单句子,适合初学者
中级 橙色 中等难度,适合有基础者
高级 红色 复杂句子,适合高级学习者

2. 练习记录数据模型

dart 复制代码
class PracticeRecord {
  final String id;              // 记录ID
  final String sentenceId;      // 句子ID
  final String sentenceText;    // 句子文本
  final DateTime practiceTime;  // 练习时间
  final int score;              // 总分
  final double pronunciation;   // 发音分数
  final double fluency;         // 流畅度分数
  final double accuracy;        // 准确度分数
  final List<String> mistakes;  // 错误列表
  final List<String> suggestions; // 建议列表

  PracticeRecord({
    required this.id,
    required this.sentenceId,
    required this.sentenceText,
    required this.practiceTime,
    required this.score,
    required this.pronunciation,
    required this.fluency,
    required this.accuracy,
    required this.mistakes,
    required this.suggestions,
  });

  // 计算属性:时间文本
  String get timeText {
    final now = DateTime.now();
    final diff = now.difference(practiceTime);
    if (diff.inMinutes < 1) return '刚刚';
    if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
    if (diff.inHours < 24) return '${diff.inHours}小时前';
    return '${diff.inDays}天前';
  }

  // 计算属性:分数颜色
  Color get scoreColor {
    if (score >= 90) return Colors.green;
    if (score >= 70) return Colors.orange;
    return Colors.red;
  }

  // 计算属性:分数等级
  String get scoreLevel {
    if (score >= 90) return '优秀';
    if (score >= 80) return '良好';
    if (score >= 70) return '及格';
    return '需加强';
  }
}

记录字段说明:

字段 类型 说明
id String 记录唯一标识
sentenceId String 关联的句子ID
sentenceText String 句子文本
practiceTime DateTime 练习时间
score int 总分(0-100)
pronunciation double 发音分数
fluency double 流畅度分数
accuracy double 准确度分数
mistakes List 错误类型列表
suggestions List 改进建议列表

计算属性:

  • timeText:相对时间显示(刚刚、X分钟前、X小时前、X天前)
  • scoreColor:根据分数返回颜色(绿色、橙色、红色)
  • scoreLevel:根据分数返回等级(优秀、良好、及格、需加强)

评分标准:

分数范围 等级 颜色 说明
90-100 优秀 绿色 发音标准,流畅自然
80-89 良好 浅绿 发音较好,略有瑕疵
70-79 及格 橙色 基本达标,需改进
0-69 需加强 红色 需要大量练习

3. 句子数据生成

dart 复制代码
void _generateSentences() {
  final random = Random();
  
  final sentences = [
    {'en': 'Hello, how are you today?', 'cn': '你好,你今天怎么样?', 'cat': '日常对话', 'level': '初级'},
    {'en': 'Nice to meet you.', 'cn': '很高兴见到你。', 'cat': '日常对话', 'level': '初级'},
    {'en': 'What do you do for a living?', 'cn': '你是做什么工作的?', 'cat': '日常对话', 'level': '初级'},
    {'en': 'Could you please send me the report?', 'cn': '你能把报告发给我吗?', 'cat': '商务英语', 'level': '中级'},
    {'en': 'Let\'s schedule a meeting for next week.', 'cn': '我们安排下周开个会吧。', 'cat': '商务英语', 'level': '中级'},
    // ... 更多句子
  ];

  for (int i = 0; i < sentences.length; i++) {
    final s = sentences[i];
    _allSentences.add(Sentence(
      id: 'sentence_$i',
      english: s['en'] as String,
      chinese: s['cn'] as String,
      category: s['cat'] as String,
      level: s['level'] as String,
      keywords: (s['en'] as String).split(' ').take(3).toList(),
      phonetic: '/ˈfəʊnətɪk/',
    ));
  }
  _applyFilters();
}

数据生成特点:

  1. 20个精选英语句子
  2. 涵盖5大分类
  3. 3个难度级别
  4. 自动提取关键词(前3个单词)
  5. 双语对照
  6. 应用初始筛选

4. 练习记录生成

dart 复制代码
void _generateRecords() {
  final random = Random();
  
  for (int i = 0; i < 8; i++) {
    final score = 60 + random.nextInt(40);
    _practiceRecords.add(PracticeRecord(
      id: 'record_$i',
      sentenceId: 'sentence_$i',
      sentenceText: _allSentences[i % _allSentences.length].english,
      practiceTime: DateTime.now().subtract(Duration(hours: random.nextInt(48))),
      score: score,
      pronunciation: 60 + random.nextDouble() * 40,
      fluency: 60 + random.nextDouble() * 40,
      accuracy: 60 + random.nextDouble() * 40,
      mistakes: score < 80 ? ['pronunciation', 'intonation'] : [],
      suggestions: score < 80 
          ? ['注意单词重音', '提高语速流畅度', '注意语调变化']
          : ['继续保持'],
    ));
  }
}

记录生成逻辑:

  1. 生成8条练习记录
  2. 分数范围:60-100分
  3. 三维评分:各60-100分
  4. 练习时间:0-48小时前随机
  5. 根据分数生成错误和建议

5. 筛选功能实现

dart 复制代码
void _applyFilters() {
  setState(() {
    _filteredSentences = _allSentences.where((sentence) {
      if (_selectedCategory != '全部' && sentence.category != _selectedCategory) {
        return false;
      }
      if (_selectedLevel != '全部' && sentence.level != _selectedLevel) {
        return false;
      }
      return true;
    }).toList();
  });
}

筛选条件:

筛选项 说明 实现方式
分类筛选 按5大分类筛选 精确匹配category字段
难度筛选 按3个级别筛选 精确匹配level字段

筛选流程:

  1. 检查分类是否匹配("全部"跳过此检查)
  2. 检查难度是否匹配("全部"跳过此检查)
  3. 更新筛选结果列表
  4. 触发UI重新渲染
dart 复制代码
bottomNavigationBar: NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() => _selectedIndex = index);
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.mic),
      label: '练习',
    ),
    NavigationDestination(
      icon: Icon(Icons.history),
      label: '记录',
    ),
    NavigationDestination(
      icon: Icon(Icons.trending_up),
      label: '进度',
    ),
  ],
),

三个页面:

页面 图标 功能
练习 mic 显示所有句子,开始练习
记录 history 显示练习历史记录
进度 trending_up 显示学习统计和进度

IndexedStack使用:

dart 复制代码
Expanded(
  child: IndexedStack(
    index: _selectedIndex,
    children: [
      _buildPracticePage(),
      _buildRecordsPage(),
      _buildProgressPage(),
    ],
  ),
),

IndexedStack的优势:

  • 保持所有页面状态
  • 切换时不重新构建
  • 提升用户体验
  • 减少资源消耗

7. ChoiceChip分类筛选

dart 复制代码
Widget _buildCategoryTabs() {
  return Container(
    height: 50,
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: _categories.length,
      itemBuilder: (context, index) {
        final category = _categories[index];
        final isSelected = category == _selectedCategory;
        return Padding(
          padding: const EdgeInsets.only(right: 8),
          child: ChoiceChip(
            label: Text(category),
            selected: isSelected,
            onSelected: (selected) {
              setState(() {
                _selectedCategory = category;
                _applyFilters();
              });
            },
          ),
        );
      },
    ),
  );
}

ChoiceChip特点:

  • Material Design 3组件
  • 自动处理选中状态样式
  • 支持单选模式
  • 适合标签筛选场景
  • 水平滚动布局

分类列表:

  • 全部、日常对话、商务英语、旅游英语、学术英语、面试英语

8. 句子卡片设计

dart 复制代码
Widget _buildSentenceCard(Sentence sentence) {
  return Card(
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => PracticeDetailPage(sentence: sentence),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                // 左侧:分类图标
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: Colors.blue.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    sentence.categoryIcon,
                    color: Colors.blue,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                // 中间:分类和难度
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        sentence.category,
                        style: const TextStyle(
                          fontSize: 14,
                          color: Colors.grey,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 2,
                        ),
                        decoration: BoxDecoration(
                          color: sentence.levelColor.withValues(alpha: 0.2),
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          sentence.level,
                          style: TextStyle(
                            fontSize: 12,
                            color: sentence.levelColor,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                // 右侧:播放按钮
                IconButton(
                  onPressed: () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('播放发音')),
                    );
                  },
                  icon: const Icon(Icons.volume_up, color: Colors.blue),
                ),
              ],
            ),
            const SizedBox(height: 16),
            // 英文句子
            Text(
              sentence.english,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                height: 1.5,
              ),
            ),
            const SizedBox(height: 8),
            // 中文翻译
            Text(
              sentence.chinese,
              style: TextStyle(
                fontSize: 14,
                color: Colors.grey[600],
              ),
            ),
            const SizedBox(height: 12),
            // 底部提示
            Row(
              children: [
                const Icon(Icons.mic, size: 16, color: Colors.grey),
                const SizedBox(width: 4),
                const Text(
                  '点击开始练习',
                  style: TextStyle(fontSize: 12, color: Colors.grey),
                ),
                const Spacer(),
                const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

卡片布局结构:

  1. 顶部行:分类图标 + 分类名称/难度标签 + 播放按钮
  2. 英文句子:粗体显示,行高1.5
  3. 中文翻译:灰色显示
  4. 底部提示:麦克风图标 + 提示文字 + 箭头

信息展示:

  • 图标:分类对应的图标和蓝色背景
  • 分类:灰色文字显示
  • 难度:彩色标签(绿/橙/红)
  • 播放:音量图标按钮
  • 句子:双语对照显示

9. 练习记录卡片

dart 复制代码
Widget _buildRecordCard(PracticeRecord record) {
  return Card(
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => RecordDetailPage(record: record),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                // 左侧:分数圆形展示
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: record.scoreColor.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        '${record.score}',
                        style: TextStyle(
                          fontSize: 24,
                          fontWeight: FontWeight.bold,
                          color: record.scoreColor,
                        ),
                      ),
                      Text(
                        '分',
                        style: TextStyle(
                          fontSize: 12,
                          color: record.scoreColor,
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(width: 16),
                // 右侧:句子和信息
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        record.sentenceText,
                        style: const TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.bold,
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 6,
                              vertical: 2,
                            ),
                            decoration: BoxDecoration(
                              color: record.scoreColor.withValues(alpha: 0.2),
                              borderRadius: BorderRadius.circular(4),
                            ),
                            child: Text(
                              record.scoreLevel,
                              style: TextStyle(
                                fontSize: 10,
                                color: record.scoreColor,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                          const SizedBox(width: 8),
                          Icon(Icons.access_time, size: 12, color: Colors.grey[600]),
                          const SizedBox(width: 4),
                          Text(
                            record.timeText,
                            style: TextStyle(fontSize: 12, color: Colors.grey[600]),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            // 三维评分展示
            Row(
              children: [
                Expanded(
                  child: _buildScoreItem(
                    '发音',
                    record.pronunciation,
                    Icons.record_voice_over,
                  ),
                ),
                Expanded(
                  child: _buildScoreItem(
                    '流畅',
                    record.fluency,
                    Icons.speed,
                  ),
                ),
                Expanded(
                  child: _buildScoreItem(
                    '准确',
                    record.accuracy,
                    Icons.check_circle,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

Widget _buildScoreItem(String label, double score, IconData icon) {
  return Column(
    children: [
      Icon(icon, size: 16, color: Colors.grey[600]),
      const SizedBox(height: 4),
      Text(
        label,
        style: TextStyle(fontSize: 12, color: Colors.grey[600]),
      ),
      const SizedBox(height: 2),
      Text(
        score.toStringAsFixed(0),
        style: const TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.bold,
          color: Colors.blue,
        ),
      ),
    ],
  );
}

记录卡片布局:

  1. 左侧:圆角矩形分数展示(60x60)
  2. 右侧:句子文本、等级标签、时间
  3. 底部:三维评分(发音、流畅、准确)

三维评分展示:

  • 发音:record_voice_over图标
  • 流畅:speed图标
  • 准确:check_circle图标
  • 分数:蓝色粗体显示

10. 进度统计页面

dart 复制代码
Widget _buildProgressPage() {
  final totalPractice = _practiceRecords.length;
  final avgScore = totalPractice > 0
      ? _practiceRecords.map((r) => r.score).reduce((a, b) => a + b) / totalPractice
      : 0.0;
  final excellentCount = _practiceRecords.where((r) => r.score >= 90).length;
  final goodCount = _practiceRecords.where((r) => r.score >= 80 && r.score < 90).length;
  final passCount = _practiceRecords.where((r) => r.score >= 70 && r.score < 80).length;

  return ListView(
    padding: const EdgeInsets.all(16),
    children: [
      // 学习统计卡片
      Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Row(
                children: [
                  Icon(Icons.analytics, color: Colors.blue),
                  SizedBox(width: 8),
                  Text(
                    '学习统计',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              Row(
                children: [
                  Expanded(
                    child: _buildStatItem(
                      '总练习',
                      '$totalPractice',
                      '次',
                      Icons.fitness_center,
                      Colors.blue,
                    ),
                  ),
                  Expanded(
                    child: _buildStatItem(
                      '平均分',
                      avgScore.toStringAsFixed(0),
                      '分',
                      Icons.star,
                      Colors.orange,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  Expanded(
                    child: _buildStatItem(
                      '优秀',
                      '$excellentCount',
                      '次',
                      Icons.emoji_events,
                      Colors.green,
                    ),
                  ),
                  Expanded(
                    child: _buildStatItem(
                      '良好',
                      '$goodCount',
                      '次',
                      Icons.thumb_up,
                      Colors.lightGreen,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
      const SizedBox(height: 16),
      // 成绩分布卡片
      Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Row(
                children: [
                  Icon(Icons.bar_chart, color: Colors.blue),
                  SizedBox(width: 8),
                  Text(
                    '成绩分布',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              _buildDistributionItem('优秀 (90-100分)', excellentCount, totalPractice, Colors.green),
              const SizedBox(height: 12),
              _buildDistributionItem('良好 (80-89分)', goodCount, totalPractice, Colors.lightGreen),
              const SizedBox(height: 12),
              _buildDistributionItem('及格 (70-79分)', passCount, totalPractice, Colors.orange),
            ],
          ),
        ),
      ),
      const SizedBox(height: 16),
      // 学习建议卡片
      Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Row(
                children: [
                  Icon(Icons.lightbulb, color: Colors.blue),
                  SizedBox(width: 8),
                  Text(
                    '学习建议',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ],
              ),
              const SizedBox(height: 12),
              _buildSuggestionItem('每天坚持练习15-30分钟'),
              _buildSuggestionItem('重点练习发音较差的句子'),
              _buildSuggestionItem('模仿标准发音,注意语调'),
              _buildSuggestionItem('录音对比,找出差距'),
              _buildSuggestionItem('循序渐进,从简单到复杂'),
            ],
          ),
        ),
      ),
    ],
  );
}

统计逻辑:

  1. 计算总练习次数
  2. 计算平均分(总分/次数)
  3. 统计优秀次数(≥90分)
  4. 统计良好次数(80-89分)
  5. 统计及格次数(70-79分)

统计项展示:

dart 复制代码
Widget _buildStatItem(String label, String value, String unit, IconData icon, Color color) {
  return Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      children: [
        Icon(icon, color: color, size: 32),
        const SizedBox(height: 8),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(
              value,
              style: TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
                color: color,
              ),
            ),
            const SizedBox(width: 4),
            Padding(
              padding: const EdgeInsets.only(bottom: 4),
              child: Text(
                unit,
                style: TextStyle(fontSize: 14, color: color),
              ),
            ),
          ],
        ),
        const SizedBox(height: 4),
        Text(
          label,
          style: const TextStyle(fontSize: 12, color: Colors.grey),
        ),
      ],
    ),
  );
}

成绩分布可视化:

dart 复制代码
Widget _buildDistributionItem(String label, int count, int total, Color color) {
  final percentage = total > 0 ? (count / total * 100) : 0.0;
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: const TextStyle(fontSize: 14)),
          Text('$count 次', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
        ],
      ),
      const SizedBox(height: 4),
      LinearProgressIndicator(
        value: percentage / 100,
        backgroundColor: Colors.grey[200],
        color: color,
        minHeight: 8,
      ),
      const SizedBox(height: 2),
      Text(
        '占比 ${percentage.toStringAsFixed(1)}%',
        style: const TextStyle(fontSize: 12, color: Colors.grey),
      ),
    ],
  );
}

11. 练习详情页

dart 复制代码
class PracticeDetailPage extends StatefulWidget {
  final Sentence sentence;

  const PracticeDetailPage({super.key, required this.sentence});

  @override
  State<PracticeDetailPage> createState() => _PracticeDetailPageState();
}

class _PracticeDetailPageState extends State<PracticeDetailPage> {
  bool _isRecording = false;
  bool _hasRecorded = false;
  bool _showResult = false;
  int _score = 0;
  double _pronunciation = 0;
  double _fluency = 0;
  double _accuracy = 0;
  List<String> _mistakes = [];
  List<String> _suggestions = [];

  void _startRecording() {
    setState(() {
      _isRecording = true;
      _hasRecorded = false;
      _showResult = false;
    });

    // 模拟录音3秒后自动停止
    Future.delayed(const Duration(seconds: 3), () {
      if (_isRecording) {
        _stopRecording();
      }
    });
  }

  void _stopRecording() {
    setState(() {
      _isRecording = false;
      _hasRecorded = true;
    });

    // 模拟评分
    _evaluatePronunciation();
  }

  void _evaluatePronunciation() {
    final random = Random();
    
    setState(() {
      _score = 60 + random.nextInt(40);
      _pronunciation = 60 + random.nextDouble() * 40;
      _fluency = 60 + random.nextDouble() * 40;
      _accuracy = 60 + random.nextDouble() * 40;
      
      if (_score < 80) {
        _mistakes = ['pronunciation', 'intonation', 'stress'];
        _suggestions = [
          '注意单词 "${widget.sentence.keywords[0]}" 的重音位置',
          '语速可以稍微放慢,保持流畅度',
          '句尾语调应该上扬,表示疑问',
          '多听标准发音,模仿语音语调',
        ];
      } else if (_score < 90) {
        _mistakes = ['intonation'];
        _suggestions = [
          '发音基本准确,继续保持',
          '注意语调的自然变化',
          '可以尝试更快的语速',
        ];
      } else {
        _mistakes = [];
        _suggestions = [
          '发音非常标准!',
          '语调自然流畅',
          '继续保持这个水平',
        ];
      }
      
      _showResult = true;
    });
  }
}

练习流程:

  1. 显示句子信息
  2. 点击开始录音
  3. 录音3秒(自动停止)
  4. 评测中(模拟AI评分)
  5. 显示结果(分数+建议)
  6. 重新录音或完成

状态管理:

  • _isRecording:是否正在录音
  • _hasRecorded:是否已录音
  • _showResult:是否显示结果
  • _score:总分
  • _pronunciation:发音分数
  • _fluency:流畅度分数
  • _accuracy:准确度分数
  • _mistakes:错误列表
  • _suggestions:建议列表

12. 录音状态展示

dart 复制代码
Widget _buildRecordingCard() {
  return Card(
    margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        children: [
          if (!_isRecording && !_hasRecorded) ...[
            const Icon(Icons.mic_none, size: 80, color: Colors.blue),
            const SizedBox(height: 16),
            const Text(
              '点击麦克风开始录音',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 24),
            ElevatedButton.icon(
              onPressed: _startRecording,
              icon: const Icon(Icons.mic, size: 32),
              label: const Text('开始录音', style: TextStyle(fontSize: 18)),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(
                  horizontal: 48,
                  vertical: 16,
                ),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(30),
                ),
              ),
            ),
          ],
          if (_isRecording) ...[
            Stack(
              alignment: Alignment.center,
              children: [
                SizedBox(
                  width: 120,
                  height: 120,
                  child: CircularProgressIndicator(
                    strokeWidth: 8,
                    valueColor: AlwaysStoppedAnimation<Color>(
                      Colors.red.withValues(alpha: 0.3),
                    ),
                  ),
                ),
                Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    color: Colors.red.withValues(alpha: 0.1),
                    shape: BoxShape.circle,
                  ),
                  child: const Icon(Icons.mic, size: 50, color: Colors.red),
                ),
              ],
            ),
            const SizedBox(height: 16),
            const Text(
              '正在录音...',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.red,
              ),
            ),
            const SizedBox(height: 24),
            ElevatedButton.icon(
              onPressed: _stopRecording,
              icon: const Icon(Icons.stop),
              label: const Text('停止录音'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(
                  horizontal: 32,
                  vertical: 12,
                ),
              ),
            ),
          ],
          if (_hasRecorded && !_showResult) ...[
            const CircularProgressIndicator(),
            const SizedBox(height: 16),
            const Text(
              '正在评测中...',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
          ],
        ],
      ),
    ),
  );
}

三种状态展示:

状态 显示内容 说明
待录音 麦克风图标 + 开始按钮 初始状态
录音中 圆形进度条 + 停止按钮 红色动画效果
评测中 加载动画 + 提示文字 模拟AI评测

录音动画:

  • 使用Stack叠加CircularProgressIndicator和麦克风图标
  • 红色半透明背景
  • 不确定进度的循环动画

13. 评分结果展示

dart 复制代码
Widget _buildResultCard() {
  final scoreColor = _score >= 90
      ? Colors.green
      : _score >= 70
          ? Colors.orange
          : Colors.red;

  return Column(
    children: [
      Card(
        margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            children: [
              const Text(
                '评测结果',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 24),
              Container(
                width: 120,
                height: 120,
                decoration: BoxDecoration(
                  color: scoreColor.withValues(alpha: 0.1),
                  shape: BoxShape.circle,
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      '$_score',
                      style: TextStyle(
                        fontSize: 48,
                        fontWeight: FontWeight.bold,
                        color: scoreColor,
                      ),
                    ),
                    Text(
                      '分',
                      style: TextStyle(
                        fontSize: 16,
                        color: scoreColor,
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 16),
              Text(
                _score >= 90
                    ? '优秀!'
                    : _score >= 80
                        ? '良好'
                        : _score >= 70
                            ? '及格'
                            : '需加强',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: scoreColor,
                ),
              ),
              const SizedBox(height: 24),
              Row(
                children: [
                  Expanded(
                    child: _buildDetailScore(
                      '发音',
                      _pronunciation,
                      Icons.record_voice_over,
                    ),
                  ),
                  Expanded(
                    child: _buildDetailScore(
                      '流畅度',
                      _fluency,
                      Icons.speed,
                    ),
                  ),
                  Expanded(
                    child: _buildDetailScore(
                      '准确度',
                      _accuracy,
                      Icons.check_circle,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
      if (_suggestions.isNotEmpty)
        Card(
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Row(
                  children: [
                    Icon(Icons.lightbulb, color: Colors.orange),
                    SizedBox(width: 8),
                    Text(
                      '改进建议',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                ..._suggestions.map((suggestion) {
                  return Padding(
                    padding: const EdgeInsets.only(bottom: 8),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Icon(Icons.arrow_right, size: 20, color: Colors.orange),
                        const SizedBox(width: 8),
                        Expanded(
                          child: Text(
                            suggestion,
                            style: const TextStyle(fontSize: 14, height: 1.5),
                          ),
                        ),
                      ],
                    ),
                  );
                }),
              ],
            ),
          ),
        ),
      Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Expanded(
              child: OutlinedButton.icon(
                onPressed: () {
                  setState(() {
                    _hasRecorded = false;
                    _showResult = false;
                  });
                },
                icon: const Icon(Icons.refresh),
                label: const Text('重新录音'),
                style: OutlinedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: ElevatedButton.icon(
                onPressed: () {
                  Navigator.pop(context);
                },
                icon: const Icon(Icons.check),
                label: const Text('完成'),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ),
          ],
        ),
      ),
    ],
  );
}

结果展示结构:

  1. 总分圆形展示(120x120)
  2. 等级文字(优秀/良好/及格/需加强)
  3. 三维评分(发音/流畅度/准确度)
  4. 改进建议列表
  5. 操作按钮(重新录音/完成)

评分颜色:

  • 90-100分:绿色(优秀)
  • 70-89分:橙色(良好/及格)
  • 0-69分:红色(需加强)

技术要点详解

1. 计算属性的高级应用

计算属性(Getter)可以根据对象状态动态返回值,避免数据冗余。

示例:

dart 复制代码
class Sentence {
  final String level;
  
  // 计算属性:难度颜色
  Color get levelColor {
    switch (level) {
      case '初级': return Colors.green;
      case '中级': return Colors.orange;
      case '高级': return Colors.red;
      default: return Colors.grey;
    }
  }
  
  // 计算属性:分类图标
  IconData get categoryIcon {
    switch (category) {
      case '日常对话': return Icons.chat;
      case '商务英语': return Icons.business;
      // ...
      default: return Icons.language;
    }
  }
}

优势:

  • 减少存储空间(不需要存储计算结果)
  • 保持数据一致性(总是基于最新数据计算)
  • 简化代码逻辑(使用时像访问属性一样)
  • 便于维护和扩展

使用场景:

  • 颜色和图标映射
  • 格式化显示
  • 状态判断
  • 数据组合

NavigationBar是Material Design 3的底部导航组件,配合IndexedStack实现页面切换。

NavigationBar特点:

  • Material Design 3风格
  • 自动处理选中状态
  • 支持图标和文字标签
  • 流畅的切换动画

IndexedStack特点:

  • 保持所有子组件状态
  • 只显示指定索引的子组件
  • 切换时不重新构建
  • 提升用户体验

配合使用:

dart 复制代码
int _selectedIndex = 0;

// 底部导航
NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() => _selectedIndex = index);
  },
  destinations: [
    NavigationDestination(icon: Icon(Icons.mic), label: '练习'),
    NavigationDestination(icon: Icon(Icons.history), label: '记录'),
    NavigationDestination(icon: Icon(Icons.trending_up), label: '进度'),
  ],
)

// 页面内容
IndexedStack(
  index: _selectedIndex,
  children: [
    PracticePage(),
    RecordsPage(),
    ProgressPage(),
  ],
)

3. ChoiceChip筛选组件

ChoiceChip是Material Design中用于单选的芯片组件。

基本用法:

dart 复制代码
ChoiceChip(
  label: Text('日常对话'),
  selected: isSelected,
  onSelected: (selected) {
    setState(() {
      _selectedCategory = '日常对话';
    });
  },
)

属性说明:

  • label:显示的文本或组件
  • selected:是否选中
  • onSelected:选中状态改变回调
  • selectedColor:选中时的颜色
  • backgroundColor:未选中时的背景色
  • labelStyle:文本样式

使用场景:

  • 分类筛选
  • 标签选择
  • 选项切换
  • 过滤条件

4. CircularProgressIndicator动画

CircularProgressIndicator用于显示圆形进度或加载动画。

确定进度模式:

dart 复制代码
CircularProgressIndicator(
  value: 0.6,  // 0.0-1.0
  strokeWidth: 8,
  color: Colors.blue,
)

不确定进度模式:

dart 复制代码
CircularProgressIndicator()  // 不指定value,显示循环动画

自定义样式:

dart 复制代码
CircularProgressIndicator(
  strokeWidth: 8,
  valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
  backgroundColor: Colors.grey[200],
)

录音动画实现:

dart 复制代码
Stack(
  alignment: Alignment.center,
  children: [
    SizedBox(
      width: 120,
      height: 120,
      child: CircularProgressIndicator(
        strokeWidth: 8,
        valueColor: AlwaysStoppedAnimation<Color>(
          Colors.red.withValues(alpha: 0.3),
        ),
      ),
    ),
    Container(
      width: 100,
      height: 100,
      decoration: BoxDecoration(
        color: Colors.red.withValues(alpha: 0.1),
        shape: BoxShape.circle,
      ),
      child: Icon(Icons.mic, size: 50, color: Colors.red),
    ),
  ],
)

使用场景:

  • 加载动画
  • 录音状态
  • 上传进度
  • 处理中提示

5. LinearProgressIndicator进度条

LinearProgressIndicator用于显示线性进度。

基本用法:

dart 复制代码
LinearProgressIndicator(
  value: 0.6,  // 0.0-1.0
  backgroundColor: Colors.grey[200],
  color: Colors.blue,
  minHeight: 8,
)

成绩分布可视化:

dart 复制代码
Widget _buildDistributionItem(String label, int count, int total, Color color) {
  final percentage = total > 0 ? (count / total * 100) : 0.0;
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label),
          Text('$count 次'),
        ],
      ),
      const SizedBox(height: 4),
      LinearProgressIndicator(
        value: percentage / 100,
        backgroundColor: Colors.grey[200],
        color: color,
        minHeight: 8,
      ),
      const SizedBox(height: 2),
      Text('占比 ${percentage.toStringAsFixed(1)}%'),
    ],
  );
}

使用场景:

  • 统计数据可视化
  • 文件上传进度
  • 任务完成度
  • 百分比展示

6. Future.delayed延迟执行

Future.delayed用于延迟执行代码,常用于模拟异步操作。

基本用法:

dart 复制代码
Future.delayed(Duration(seconds: 3), () {
  print('3秒后执行');
});

录音自动停止:

dart 复制代码
void _startRecording() {
  setState(() {
    _isRecording = true;
  });

  // 模拟录音3秒后自动停止
  Future.delayed(const Duration(seconds: 3), () {
    if (_isRecording) {
      _stopRecording();
    }
  });
}

模拟AI评测:

dart 复制代码
void _stopRecording() {
  setState(() {
    _isRecording = false;
    _hasRecorded = true;
  });

  // 延迟1秒模拟评测
  Future.delayed(const Duration(seconds: 1), () {
    _evaluatePronunciation();
  });
}

使用场景:

  • 模拟网络请求
  • 延迟跳转
  • 自动关闭提示
  • 定时任务

7. Random随机数生成

Random用于生成随机数,常用于模拟数据。

基本用法:

dart 复制代码
final random = Random();

// 生成0-99的随机整数
int randomInt = random.nextInt(100);

// 生成0.0-1.0的随机小数
double randomDouble = random.nextDouble();

// 生成随机布尔值
bool randomBool = random.nextBool();

评分生成:

dart 复制代码
void _evaluatePronunciation() {
  final random = Random();
  
  setState(() {
    _score = 60 + random.nextInt(40);  // 60-100分
    _pronunciation = 60 + random.nextDouble() * 40;  // 60-100分
    _fluency = 60 + random.nextDouble() * 40;
    _accuracy = 60 + random.nextDouble() * 40;
  });
}

使用场景:

  • 模拟评分
  • 测试数据生成
  • 随机推荐
  • 抽奖功能

8. DateTime时间处理

DateTime用于处理日期和时间。

获取当前时间:

dart 复制代码
DateTime now = DateTime.now();

时间计算:

dart 复制代码
// 减去时间
DateTime past = DateTime.now().subtract(Duration(hours: 2));

// 增加时间
DateTime future = DateTime.now().add(Duration(days: 7));

// 时间差
Duration diff = DateTime.now().difference(past);

相对时间显示:

dart 复制代码
String get timeText {
  final now = DateTime.now();
  final diff = now.difference(practiceTime);
  if (diff.inMinutes < 1) return '刚刚';
  if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
  if (diff.inHours < 24) return '${diff.inHours}小时前';
  return '${diff.inDays}天前';
}

使用场景:

  • 练习时间记录
  • 相对时间显示
  • 时间排序
  • 有效期判断

9. List高级操作

List提供了丰富的操作方法。

map映射:

dart 复制代码
// 提取所有分数
List<int> scores = _practiceRecords.map((r) => r.score).toList();

reduce归约:

dart 复制代码
// 计算总分
int totalScore = scores.reduce((a, b) => a + b);

// 计算平均分
double avgScore = totalScore / scores.length;

where筛选:

dart 复制代码
// 筛选优秀记录
List<PracticeRecord> excellent = _practiceRecords
    .where((r) => r.score >= 90)
    .toList();

take截取:

dart 复制代码
// 提取前3个单词作为关键词
List<String> keywords = sentence.split(' ').take(3).toList();

使用场景:

  • 数据统计
  • 列表筛选
  • 数据转换
  • 关键词提取

10. withValues透明度设置

withValues是Flutter 3.27+版本中设置颜色透明度的新方法。

新旧对比:

dart 复制代码
// 旧方法(已废弃)
Colors.blue.withOpacity(0.5)

// 新方法
Colors.blue.withValues(alpha: 0.5)

完整用法:

dart 复制代码
// 只设置透明度
Colors.blue.withValues(alpha: 0.5)

// 设置多个通道
Colors.blue.withValues(
  alpha: 0.5,
  red: 0.8,
  green: 0.6,
  blue: 0.4,
)

alpha参数范围:

  • 0.0:完全透明
  • 0.5:半透明
  • 1.0:完全不透明

使用场景:

  • 背景色透明
  • 阴影颜色
  • 遮罩效果
  • 渐变色

功能扩展方向

1. 接入真实语音识别API

当前应用使用模拟评分,可以接入真实的语音识别和评测API。

实现步骤:

  1. 选择语音识别服务(如讯飞、百度、Google)
  2. 添加录音权限
  3. 集成语音识别SDK
  4. 实现录音功能
  5. 调用评测API
  6. 解析评测结果

示例代码:

dart 复制代码
// 添加依赖
dependencies:
  speech_to_text: ^6.0.0
  permission_handler: ^11.0.0

// 使用示例
import 'package:speech_to_text/speech_to_text.dart';

class SpeechRecognition {
  final SpeechToText _speech = SpeechToText();
  
  Future<void> initialize() async {
    bool available = await _speech.initialize();
    if (!available) {
      print('语音识别不可用');
    }
  }
  
  Future<void> startListening() async {
    await _speech.listen(
      onResult: (result) {
        String recognizedText = result.recognizedWords;
        // 处理识别结果
      },
    );
  }
  
  Future<void> stopListening() async {
    await _speech.stop();
  }
}

2. 添加音标显示

为每个句子添加国际音标(IPA),帮助用户学习正确发音。

实现方案:

  1. 为每个句子添加音标数据
  2. 使用特殊字体显示音标
  3. 点击单词显示音标
  4. 音标与发音对照
  5. 重音标记

功能特点:

  • 完整句子音标
  • 单词音标查询
  • 重音位置标注
  • 连读规则提示
  • 音标学习模式

3. 语音波形可视化

实时显示录音时的语音波形,增强视觉反馈。

实现方案:

  1. 获取音频数据
  2. 使用CustomPainter绘制波形
  3. 实时更新波形
  4. 波形颜色渐变
  5. 音量大小显示

功能特点:

  • 实时波形显示
  • 音量可视化
  • 录音状态反馈
  • 美观的动画效果

4. 发音对比功能

将用户发音与标准发音进行对比,直观显示差异。

实现方案:

  1. 播放标准发音
  2. 播放用户录音
  3. 波形对比显示
  4. 差异高亮标注
  5. 逐句对比

功能特点:

  • 双波形对比
  • 差异可视化
  • 慢速播放
  • 循环播放
  • 片段重复

5. 学习计划制定

根据用户水平制定个性化学习计划。

实现方案:

  1. 评估用户水平
  2. 制定学习目标
  3. 推荐练习内容
  4. 每日任务提醒
  5. 进度跟踪

功能特点:

  • 水平测试
  • 目标设定
  • 智能推荐
  • 每日打卡
  • 进度报告

6. 社交分享功能

分享学习成果到社交平台,增加学习动力。

实现方案:

  1. 生成成绩卡片
  2. 添加分享按钮
  3. 集成分享SDK
  4. 自定义分享内容
  5. 排行榜功能

功能特点:

  • 成绩卡片生成
  • 一键分享
  • 好友PK
  • 排行榜
  • 学习勋章

7. 离线模式

支持离线练习,无需网络也能学习。

实现方案:

  1. 本地存储句子数据
  2. 本地存储音频文件
  3. 离线评分算法
  4. 数据同步机制
  5. 缓存管理

功能特点:

  • 离线练习
  • 本地评分
  • 数据缓存
  • 自动同步
  • 节省流量

8. 多语言支持

扩展到其他语言学习,如日语、韩语、法语等。

实现方案:

  1. 多语言数据结构
  2. 语言切换功能
  3. 不同语言评测
  4. 语言特色功能
  5. 国际化支持

功能特点:

  • 多语言选择
  • 语言切换
  • 专属评测
  • 文化学习
  • 全球化

常见问题解答

1. 如何接入真实的语音识别?

问题:应用中的评分是模拟的,如何接入真实语音识别?

解答:

  1. 选择服务商:讯飞、百度、Google、Azure
  2. 申请API密钥:注册开发者账号获取密钥
  3. 添加依赖包:speech_to_text、permission_handler
  4. 请求录音权限:Android和iOS权限配置
  5. 集成SDK:按照官方文档集成

示例:

dart 复制代码
// 添加依赖
dependencies:
  speech_to_text: ^6.0.0
  permission_handler: ^11.0.0

// 请求权限
Future<bool> requestPermission() async {
  var status = await Permission.microphone.request();
  return status.isGranted;
}

// 初始化语音识别
final SpeechToText _speech = SpeechToText();
bool available = await _speech.initialize();

// 开始识别
await _speech.listen(
  onResult: (result) {
    String text = result.recognizedWords;
    // 处理识别结果
  },
);

2. 如何实现真实的发音评分?

问题:如何对用户发音进行准确评分?

解答:

  1. 语音识别:将语音转为文本
  2. 文本对比:与标准文本对比
  3. 发音评测API:使用专业评测服务
  4. 音素分析:分析每个音素的准确度
  5. 综合评分:发音、流畅度、完整度

评测维度:

  • 发音准确度:音素级别评测
  • 流畅度:语速、停顿分析
  • 完整度:是否完整朗读
  • 重音:重音位置是否正确
  • 语调:语调变化是否自然

3. 如何优化录音体验?

问题:录音功能如何做得更好用?

解答:

  1. 实时反馈:显示音量波形
  2. 倒计时提示:录音前3秒倒计时
  3. 自动停止:检测静音自动停止
  4. 重录功能:方便重新录音
  5. 播放对比:播放录音与标准发音

优化建议:

dart 复制代码
// 音量检测
void _checkVolume(double volume) {
  if (volume < 0.1) {
    _silentDuration++;
    if (_silentDuration > 30) {  // 3秒静音
      _stopRecording();
    }
  } else {
    _silentDuration = 0;
  }
}

// 倒计时
void _startCountdown() {
  for (int i = 3; i > 0; i--) {
    Future.delayed(Duration(seconds: 3 - i), () {
      setState(() => _countdown = i);
    });
  }
  Future.delayed(Duration(seconds: 3), () {
    _startRecording();
  });
}

4. 如何实现学习进度追踪?

问题:如何记录和展示学习进度?

解答:

  1. 本地存储:使用SharedPreferences或SQLite
  2. 数据统计:练习次数、平均分、进步曲线
  3. 可视化展示:图表展示学习数据
  4. 目标设定:设置学习目标和提醒
  5. 成就系统:解锁成就和勋章

示例:

dart 复制代码
// 保存练习记录
Future<void> savePracticeRecord(PracticeRecord record) async {
  final prefs = await SharedPreferences.getInstance();
  List<String> records = prefs.getStringList('practice_records') ?? [];
  records.add(jsonEncode(record.toJson()));
  await prefs.setStringList('practice_records', records);
}

// 统计数据
Map<String, dynamic> getStatistics() {
  return {
    'totalPractice': _practiceRecords.length,
    'avgScore': _calculateAvgScore(),
    'bestScore': _getBestScore(),
    'improvement': _calculateImprovement(),
  };
}

5. 如何提高应用性能?

问题:应用卡顿,如何优化性能?

解答:

  1. 列表优化:使用ListView.builder懒加载
  2. 图片优化:使用合适尺寸的图片
  3. 状态管理:合理使用setState范围
  4. 异步加载:使用FutureBuilder
  5. 减少重建:使用const构造函数

优化技巧:

dart 复制代码
// 1. 使用const构造函数
const Text('标题')  // 不会重建

// 2. 使用ListView.builder
ListView.builder(
  itemCount: sentences.length,
  itemBuilder: (context, index) {
    return SentenceCard(sentence: sentences[index]);
  },
)

// 3. 避免在build中创建对象
// 错误示例
Widget build(BuildContext context) {
  final list = [1, 2, 3];  // 每次build都创建
  return ListView(children: list.map((i) => Text('$i')).toList());
}

// 正确示例
final list = [1, 2, 3];  // 在类级别定义
Widget build(BuildContext context) {
  return ListView(children: list.map((i) => Text('$i')).toList());
}

// 4. 使用FutureBuilder异步加载
FutureBuilder<List<Sentence>>(
  future: loadSentences(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return SentenceList(sentences: snapshot.data!);
    }
    return CircularProgressIndicator();
  },
)

项目总结

核心功能流程

筛选
点击句子
切换页面
重新录音
完成
启动应用
生成句子数据
显示练习页
用户操作
选择分类/难度
进入练习详情
记录/进度
更新筛选结果
开始录音
录音3秒
停止录音
AI评测
显示结果
用户选择
显示统计数据

数据流转

筛选
练习
原始数据
Sentence模型
_allSentences列表
筛选逻辑
_filteredSentences
UI展示
用户交互
录音评分
PracticeRecord
_practiceRecords
统计分析

技术架构

Flutter应用
UI层
数据层
业务逻辑层
MaterialApp
NavigationBar
页面组件
Sentence模型
PracticeRecord模型
状态管理
筛选逻辑
录音评分
统计计算

项目特色

  1. Material Design 3:采用最新设计规范,界面美观现代
  2. 计算属性:充分利用Getter实现动态数据,代码简洁
  3. 模拟AI评测:完整的评分系统,包含三维评分和建议
  4. 录音动画:CircularProgressIndicator实现录音状态动画
  5. 数据可视化:LinearProgressIndicator展示统计数据
  6. 响应式设计:适配不同屏幕尺寸
  7. 无需依赖:纯Flutter实现,无需额外包
  8. 模块化设计:组件复用,代码结构清晰
  9. 用户体验:流畅的动画和交互

学习收获

通过本项目,你将学会:

  1. 数据建模:设计合理的数据模型和计算属性
  2. 列表展示:使用ListView.builder高效展示数据
  3. 筛选功能:实现多条件组合筛选逻辑
  4. 底部导航:使用NavigationBar和IndexedStack
  5. 录音模拟:实现录音状态和动画效果
  6. 评分系统:设计三维评分和建议生成
  7. 数据统计:实现学习进度统计和可视化
  8. 状态管理:使用setState管理应用状态
  9. UI组件:掌握Card、Chip、CircularProgressIndicator等组件
  10. 布局技巧:Row、Column、Stack等布局组合
  11. 交互设计:实现录音、评分、查看记录等交互功能
  12. 动画效果:CircularProgressIndicator录音动画

性能优化建议

  1. 列表优化:

    • 使用ListView.builder懒加载
    • 避免在itemBuilder中创建复杂对象
    • 使用const构造函数
  2. 状态管理:

    • 合理使用setState范围
    • 考虑使用Provider等状态管理方案
    • 避免不必要的重建
  3. 数据处理:

    • 使用异步加载大量数据
    • 实现数据缓存
    • 优化筛选算法
  4. 内存管理:

    • 及时释放不用的资源
    • 避免内存泄漏
    • 使用弱引用
  5. 动画优化:

    • 使用AnimatedWidget
    • 避免过度动画
    • 控制动画帧率

未来优化方向

  1. 功能增强:

    • 接入真实语音识别API
    • 添加音标显示
    • 语音波形可视化
    • 发音对比功能
    • 学习计划制定
  2. 用户体验:

    • 添加骨架屏
    • 优化加载动画
    • 实现下拉刷新
    • 添加空状态页面
    • 错误处理优化
  3. 数据管理:

    • 实现离线模式
    • 添加数据同步
    • 实现数据备份
    • 优化缓存策略
  4. 社交功能:

    • 成绩分享
    • 好友PK
    • 排行榜
    • 学习社区
  5. 个性化:

    • 推荐算法
    • 学习计划
    • 个性化首页
    • 主题切换

本项目展示了如何使用Flutter构建一个功能完整的英语口语评测应用,涵盖了数据建模、录音模拟、智能评分、统计可视化等核心功能。通过学习本项目,你将掌握Flutter应用开发的基本技能,为开发更复杂的应用打下坚实基础。

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

相关推荐
LinXunFeng4 小时前
Draw Things 换装 LoRA 实战:用 Silo 秒装,同一提示词对比出图
人工智能·开源·aigc
xiaohaiAIgeo4 小时前
【2026年】通风柜自动门安全吗?联锁逻辑与防夹设计
人工智能·安全·科普知识
像风一样自由20204 小时前
52.Docker部署大模型应用从本地开发到服务化运行
运维·人工智能·docker·容器·大模型·智能体
牛战士coding4 小时前
AI Agent 如何接入真实业务系统?订单助手三层架构完整拆解
人工智能·架构
·云扬·4 小时前
Prompt 工程与安全:从 System 设计到注入防御
java·人工智能·安全·ai·prompt
阿明副业观察4 小时前
2026年免费AI视频生成工具功能深度解析:有无水印、中文语音及商业用途考量
大数据·人工智能·ai作画·aigc·音视频·ai写作
收放扳机5 小时前
PCB收放板设备机械手架构选型参考——三轴/四轴/六轴/蜘蛛手对比
人工智能·科技·架构·自动化·制造·pcb工艺
AKAMAI5 小时前
Akamai 宣布与Anthropic达成 116 亿美元多年期协议,以支持其日益增长的需求
人工智能·云计算
甲维斯5 小时前
我把“赶 Deadline”和AI做成了一款游戏
人工智能·游戏开发
Smoothcloud润云5 小时前
PyTorch大模型权重加载与启动耗时优化实战:定位磁盘、内存与显存瓶颈
人工智能·pytorch·python·深度学习·gpu算力