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

相关推荐
top_designer2 小时前
手绘贴图画断手?“AI 炼金术”3分钟量产风格化材质
人工智能·游戏·3d·材质·设计师·游戏策划·游戏美术
Hcoco_me2 小时前
大模型面试题88:cuda core的数量 与 开发算子中实际使用的线程 关系是什么?过量线程会发生什么情况?
人工智能·深度学习·机器学习·chatgpt·职场和发展·机器人
Pyeako2 小时前
opencv计算机视觉--图形旋转&图形可视化&均衡化
人工智能·python·opencv·计算机视觉·图形旋转·图形可视化·均衡化
木斯佳2 小时前
HarmonyOS 6实战(源码教学篇)— AVSession Kit 新特性【仿某云音乐实现媒体会话和后台播放管理】【API20】
华为·harmonyos·媒体
@我不是大鹏2 小时前
3、Spring AI Alibaba(SAA)零基础速通实战之Ollama私有化部署和对接本地大模型
数据库·人工智能·spring
人工智能AI技术2 小时前
【Agent从入门到实践】28 开发第一个Agent——开发准备:环境搭建(Python、依赖库、大模型API密钥)
人工智能·python
Miguo94well2 小时前
Flutter框架跨平台鸿蒙开发——失物招领APP的开发流程
flutter·华为·harmonyos
实时云渲染dlxyz66882 小时前
鸿蒙系统下,点盾云播放器使用一段时间后忽然读取不到视频解决方法
音视频·harmonyos·点盾云播放·纯鸿蒙系统播放·应用权限授权
小风呼呼吹儿2 小时前
Flutter 框架跨平台鸿蒙开发 - 全国公积金查询:智能公积金管理助手
flutter·华为·harmonyos