Flutter健身训练计划开发教程

项目简介

健身训练计划是一款专业的健身管理应用,帮助用户制定个性化训练计划、追踪训练进度、记录健身成果。应用采用现代化的Material Design 3设计语言,提供直观友好的用户界面和完整的健身管理功能。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心功能特性

  • 今日训练管理:实时显示当日训练计划,支持快速开始训练
  • 训练计划制定:创建个性化训练计划,包含多种训练类型和难度级别
  • 训练进度追踪:详细记录每次训练情况,支持评分和备注
  • 智能统计分析:训练数据统计、卡路里消耗、连续训练天数等
  • 个性化设置:训练提醒、身体数据管理、数据备份等功能

技术架构特点

  • 响应式设计:适配不同屏幕尺寸,提供一致的用户体验
  • 状态管理:使用StatefulWidget进行本地状态管理
  • 数据模型:完整的数据结构设计,支持复杂的健身业务逻辑
  • 动画效果:流畅的页面切换和交互动画
  • 模块化架构:清晰的代码结构,便于维护和扩展

项目架构设计

整体架构图

FitnessTrainingApp

FitnessTrainingHomePage

今日训练页面

训练计划页面

训练进度页面

设置页面

数据模型层

TrainingPlan

Exercise

TrainingRecord

枚举类型

UI组件层

添加计划对话框

计划详情对话框

训练对话框

练习对话框

工具方法层

日期格式化

状态管理

数据筛选

统计计算

数据流架构

用户操作

状态更新

数据处理

UI重建

界面更新

本地数据

内存缓存

业务逻辑

界面渲染

数据模型设计

核心数据结构

1. 训练计划模型(TrainingPlan)
class TrainingPlan {
  final String id;                    // 唯一标识
  final String name;                  // 计划名称
  final String description;           // 计划描述
  final TrainingLevel level;          // 训练难度
  final TrainingType type;            // 训练类型
  final int durationWeeks;            // 持续周数
  final int sessionsPerWeek;          // 每周训练次数
  final List<String> targetMuscles;   // 目标肌群
  final List<Exercise> exercises;     // 包含的练习
  final String imageUrl;              // 计划图片
  final DateTime createdDate;         // 创建日期
  final bool isActive;                // 是否激活
  final String notes;                 // 备注
  final int estimatedCalories;        // 预计消耗卡路里
  final int estimatedDuration;        // 预计训练时长(分钟)
}
2. 运动模型(Exercise)
class Exercise {
  final String id;                    // 练习ID
  final String name;                  // 练习名称
  final String description;           // 练习描述
  final ExerciseType type;            // 练习类型
  final MuscleGroup primaryMuscle;    // 主要肌群
  final List<MuscleGroup> secondaryMuscles; // 次要肌群
  final EquipmentType equipment;      // 所需器械
  final DifficultyLevel difficulty;   // 难度等级
  final int sets;                     // 组数
  final int reps;                     // 每组次数
  final int restSeconds;              // 休息时间(秒)
  final double weight;                // 重量(kg)
  final int duration;                 // 持续时间(秒,用于有氧运动)
  final String instructions;          // 动作要领
  final List<String> tips;            // 训练技巧
  final String videoUrl;              // 视频链接
  final String imageUrl;              // 图片链接
  final int caloriesPerRep;           // 每次消耗卡路里
}
3. 训练记录模型(TrainingRecord)
class TrainingRecord {
  final String id;                    // 记录ID
  final String planId;                // 关联计划ID
  final String exerciseId;            // 关联练习ID
  final DateTime date;                // 训练日期
  final int completedSets;            // 完成组数
  final int completedReps;            // 完成次数
  final double actualWeight;          // 实际重量
  final int actualDuration;           // 实际时长
  final int caloriesBurned;           // 消耗卡路里
  final double rating;                // 感受评分(1-5星)
  final String notes;                 // 备注
  final TrainingStatus status;        // 训练状态
  final DateTime startTime;           // 开始时间
  final DateTime? endTime;            // 结束时间
}

枚举类型定义

训练难度枚举
enum TrainingLevel {
  beginner,    // 初级
  intermediate, // 中级
  advanced,    // 高级
  expert,      // 专家级
}
训练类型枚举
enum TrainingType {
  strength,    // 力量训练
  cardio,      // 有氧训练
  flexibility, // 柔韧性训练
  mixed,       // 混合训练
  hiit,        // 高强度间歇训练
  yoga,        // 瑜伽
  pilates,     // 普拉提
}
练习类型枚举
enum ExerciseType {
  strength,    // 力量
  cardio,      // 有氧
  flexibility, // 柔韧
  balance,     // 平衡
  plyometric,  // 爆发力
}
肌群分类枚举
enum MuscleGroup {
  chest,       // 胸部
  back,        // 背部
  shoulders,   // 肩部
  arms,        // 手臂
  core,        // 核心
  legs,        // 腿部
  glutes,      // 臀部
  calves,      // 小腿
  fullBody,    // 全身
}
器械类型枚举
enum EquipmentType {
  none,        // 无器械
  dumbbells,   // 哑铃
  barbell,     // 杠铃
  kettlebell,  // 壶铃
  resistance,  // 阻力带
  machine,     // 器械
  cardio,      // 有氧器械
  mat,         // 瑜伽垫
}
训练状态枚举
enum TrainingStatus {
  planned,     // 计划中
  inProgress,  // 进行中
  completed,   // 已完成
  skipped,     // 跳过
  failed,      // 失败
}

核心功能实现

1. 今日训练管理

今日训练页面是应用的核心功能,提供当日训练计划的完整视图和快速操作入口。

页面结构设计
Widget _buildTodayPage() {
  return Column(
    children: [
      _buildTodayHeader(),      // 顶部统计卡片
      Expanded(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              _buildTodayStats(),     // 今日概览统计
              _buildTodayWorkout(),   // 训练计划展示
              _buildQuickActions(),   // 快速操作面板
            ],
          ),
        ),
      ),
    ],
  );
}
统计数据计算
void _updateStatistics() {
  setState(() {
    _totalPlans = _trainingPlans.length;
    _activePlans = _trainingPlans.where((p) => p.isActive).length;
    _completedWorkouts = _records.where((r) => r.status == TrainingStatus.completed).length;
    _totalCaloriesBurned = _records.fold(0, (sum, r) => sum + r.caloriesBurned);
    
    final completedRecords = _records.where((r) => r.status == TrainingStatus.completed).toList();
    if (completedRecords.isNotEmpty) {
      _averageRating = completedRecords.fold(0.0, (sum, r) => sum + r.rating) / completedRecords.length;
    }
    
    _currentStreak = _calculateCurrentStreak();
  });
}
连续训练天数计算
int _calculateCurrentStreak() {
  final sortedRecords = _records
      .where((r) => r.status == TrainingStatus.completed)
      .toList()
    ..sort((a, b) => b.date.compareTo(a.date));

  if (sortedRecords.isEmpty) return 0;

  int streak = 0;
  DateTime currentDate = DateTime.now();
  
  for (final record in sortedRecords) {
    final recordDate = DateTime(record.date.year, record.date.month, record.date.day);
    final checkDate = DateTime(currentDate.year, currentDate.month, currentDate.day);
    
    if (recordDate == checkDate || recordDate == checkDate.subtract(const Duration(days: 1))) {
      streak++;
      currentDate = record.date.subtract(const Duration(days: 1));
    } else {
      break;
    }
  }
  
  return streak;
}
今日训练展示
Widget _buildTodayWorkout() {
  final todayRecords = _getTodayRecords();
  final activePlan = _getActivePlan();

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text('今日训练', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              if (activePlan != null)
                TextButton(
                  onPressed: () => _showPlanDetails(activePlan),
                  child: const Text('查看计划'),
                ),
            ],
          ),
          if (activePlan == null)
            _buildEmptyState()
          else ...[
            _buildPlanSummary(activePlan),
            const SizedBox(height: 16),
            const Text('今日练习', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            ...activePlan.exercises.take(3).map((exercise) {
              final record = todayRecords.firstWhere(
                (r) => r.exerciseId == exercise.id,
                orElse: () => _createDefaultRecord(activePlan.id, exercise.id),
              );
              return _buildExerciseItem(exercise, record);
            }),
          ],
        ],
      ),
    ),
  );
}

2. 训练计划管理

训练计划功能提供完整的计划创建、编辑和管理功能。

计划列表展示
Widget _buildPlanCard(TrainingPlan plan) {
  return Card(
    child: InkWell(
      onTap: () => _showPlanDetails(plan),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                // 计划图标
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: _getTrainingTypeColor(plan.type).withValues(alpha: 0.2),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(_getTrainingTypeIcon(plan.type)),
                ),
                const SizedBox(width: 16),
                // 计划信息
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(plan.name, style: TextStyle(fontWeight: FontWeight.bold)),
                      Text('${plan.estimatedDuration}分钟 • ${plan.estimatedCalories}卡路里'),
                      Text(plan.description, maxLines: 1, overflow: TextOverflow.ellipsis),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            // 标签和操作按钮
            Row(
              children: [
                _buildLevelChip(plan.level),
                const SizedBox(width: 8),
                _buildTypeChip(plan.type),
                const Spacer(),
                if (plan.isActive)
                  ElevatedButton(
                    onPressed: () => _startWorkout(plan),
                    child: const Text('开始训练'),
                  ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}
添加计划对话框
class _AddPlanDialog extends StatefulWidget {
  final Function(TrainingPlan) onSave;

  
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('创建训练计划'),
      content: SizedBox(
        width: 400,
        height: 600,
        child: Form(
          key: _formKey,
          child: SingleChildScrollView(
            child: Column(
              children: [
                // 计划名称输入
                TextFormField(
                  controller: _nameController,
                  decoration: const InputDecoration(
                    labelText: '计划名称 *',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.fitness_center),
                  ),
                  validator: (value) => value?.isEmpty ?? true ? '请输入计划名称' : null,
                ),
                // 计划描述输入
                TextFormField(
                  controller: _descriptionController,
                  decoration: const InputDecoration(
                    labelText: '计划描述 *',
                    border: OutlineInputBorder(),
                  ),
                  maxLines: 2,
                  validator: (value) => value?.isEmpty ?? true ? '请输入计划描述' : null,
                ),
                // 难度和类型选择
                Row(
                  children: [
                    Expanded(
                      child: DropdownButtonFormField<TrainingLevel>(
                        value: _level,
                        decoration: const InputDecoration(labelText: '训练难度'),
                        items: TrainingLevel.values.map((level) => 
                          DropdownMenuItem(value: level, child: Text(_getTrainingLevelName(level)))).toList(),
                        onChanged: (value) => setState(() => _level = value!),
                      ),
                    ),
                    Expanded(
                      child: DropdownButtonFormField<TrainingType>(
                        value: _type,
                        decoration: const InputDecoration(labelText: '训练类型'),
                        items: TrainingType.values.map((type) =>
                          DropdownMenuItem(value: type, child: Text(_getTrainingTypeName(type)))).toList(),
                        onChanged: (value) => setState(() => _type = value!),
                      ),
                    ),
                  ],
                ),
                // 持续时间和频率设置
                Row(
                  children: [
                    Expanded(
                      child: TextFormField(
                        initialValue: _durationWeeks.toString(),
                        decoration: const InputDecoration(labelText: '持续周数', suffixText: '周'),
                        keyboardType: TextInputType.number,
                        onChanged: (value) => _durationWeeks = int.tryParse(value) ?? 4,
                      ),
                    ),
                    Expanded(
                      child: TextFormField(
                        initialValue: _sessionsPerWeek.toString(),
                        decoration: const InputDecoration(labelText: '每周次数', suffixText: '次'),
                        keyboardType: TextInputType.number,
                        onChanged: (value) => _sessionsPerWeek = int.tryParse(value) ?? 3,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: Text('取消')),
        ElevatedButton(onPressed: _savePlan, child: Text('保存')),
      ],
    );
  }
}

3. 训练执行功能

训练执行功能提供完整的训练过程管理,包括练习指导、进度追踪和结果记录。

训练对话框
class _WorkoutDialog extends StatefulWidget {
  final TrainingPlan plan;
  final Function(List<TrainingRecord>) onComplete;

  
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('训练:${widget.plan.name}'),
      content: SizedBox(
        width: 400,
        height: 400,
        child: Column(
          children: [
            // 进度条
            LinearProgressIndicator(
              value: (_currentExerciseIndex + 1) / widget.plan.exercises.length,
            ),
            const SizedBox(height: 16),
            Text('第${_currentExerciseIndex + 1}/${widget.plan.exercises.length}个练习'),
            const SizedBox(height: 16),
            // 当前练习展示
            if (widget.plan.exercises.isNotEmpty) ...[
              _buildCurrentExercise(),
              const SizedBox(height: 16),
              // 休息计时器或练习控制
              if (_isResting)
                _buildRestTimer()
              else
                _buildExerciseControls(),
            ],
          ],
        ),
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: Text('退出训练')),
        if (_currentExerciseIndex >= widget.plan.exercises.length - 1)
          ElevatedButton(onPressed: _completeWorkout, child: Text('完成训练')),
      ],
    );
  }
}
休息计时器
Widget _buildRestTimer() {
  return Column(
    children: [
      const Text('休息时间', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
      const SizedBox(height: 16),
      Text('$_restTimeRemaining秒', style: TextStyle(fontSize: 32, color: Colors.orange)),
      const SizedBox(height: 16),
      ElevatedButton(onPressed: _skipRest, child: Text('跳过休息')),
    ],
  );
}

void _startRestTimer() {
  Future.delayed(const Duration(seconds: 1), () {
    if (_isResting && _restTimeRemaining > 0) {
      setState(() => _restTimeRemaining--);
      _startRestTimer();
    } else if (_isResting) {
      _skipRest();
    }
  });
}
练习完成记录
void _completeCurrentExercise() {
  final exercise = widget.plan.exercises[_currentExerciseIndex];
  final record = TrainingRecord(
    id: DateTime.now().millisecondsSinceEpoch.toString(),
    planId: widget.plan.id,
    exerciseId: exercise.id,
    date: DateTime.now(),
    completedSets: exercise.sets,
    completedReps: exercise.reps * exercise.sets,
    actualDuration: exercise.duration,
    caloriesBurned: exercise.caloriesPerRep * exercise.reps * exercise.sets,
    rating: 4.0,
    status: TrainingStatus.completed,
    startTime: DateTime.now().subtract(const Duration(minutes: 5)),
    endTime: DateTime.now(),
  );

  _records.add(record);

  if (_currentExerciseIndex < widget.plan.exercises.length - 1) {
    setState(() {
      _isResting = true;
      _restTimeRemaining = exercise.restSeconds;
    });
    _startRestTimer();
  } else {
    _completeWorkout();
  }
}

4. 训练进度追踪

训练进度功能提供详细的训练数据统计和可视化分析。

进度统计计算
int _getWeeklyWorkouts() {
  final now = DateTime.now();
  final weekStart = now.subtract(Duration(days: now.weekday - 1));
  return _records
      .where((r) =>
          r.status == TrainingStatus.completed &&
          r.date.isAfter(weekStart) &&
          r.date.isBefore(now.add(const Duration(days: 1))))
      .length;
}

int _getAverageWorkoutDuration() {
  final completedRecords = _records
      .where((r) => r.status == TrainingStatus.completed && r.endTime != null)
      .toList();

  if (completedRecords.isEmpty) return 0;

  final totalMinutes = completedRecords.fold(0, (sum, r) {
    final duration = r.endTime!.difference(r.startTime).inMinutes;
    return sum + duration;
  });

  return totalMinutes ~/ completedRecords.length;
}

int _getLongestStreak() {
  final sortedRecords = _records
      .where((r) => r.status == TrainingStatus.completed)
      .toList()
    ..sort((a, b) => a.date.compareTo(b.date));

  if (sortedRecords.isEmpty) return 0;

  int maxStreak = 1;
  int currentStreak = 1;
  DateTime lastDate = sortedRecords.first.date;

  for (int i = 1; i < sortedRecords.length; i++) {
    final currentDate = sortedRecords[i].date;
    final daysDiff = currentDate.difference(lastDate).inDays;

    if (daysDiff == 1) {
      currentStreak++;
      maxStreak = maxStreak > currentStreak ? maxStreak : currentStreak;
    } else if (daysDiff > 1) {
      currentStreak = 1;
    }

    lastDate = currentDate;
  }

  return maxStreak;
}
进度数据展示
Widget _buildProgressStats() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('统计数据', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          const SizedBox(height: 16),
          GridView.count(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
            childAspectRatio: 1.5,
            children: [
              _buildProgressStatCard('总训练次数', '$_completedWorkouts', Icons.fitness_center, Colors.orange, '次'),
              _buildProgressStatCard('总卡路里', '$_totalCaloriesBurned', Icons.whatshot, Colors.red, 'kcal'),
              _buildProgressStatCard('平均时长', '${_getAverageWorkoutDuration()}', Icons.schedule, Colors.blue, '分钟'),
              _buildProgressStatCard('最长连续', '${_getLongestStreak()}', Icons.local_fire_department, Colors.purple, '天'),
            ],
          ),
        ],
      ),
    ),
  );
}
训练记录展示
Widget _buildRecordItem(TrainingRecord record, Exercise exercise) {
  return Container(
    margin: const EdgeInsets.only(bottom: 12),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: _getStatusColor(record.status).withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: _getStatusColor(record.status).withValues(alpha: 0.3)),
    ),
    child: Row(
      children: [
        // 练习图标
        Container(
          width: 40,
          height: 40,
          decoration: BoxDecoration(
            color: _getMuscleGroupColor(exercise.primaryMuscle).withValues(alpha: 0.2),
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(_getMuscleGroupIcon(exercise.primaryMuscle)),
        ),
        const SizedBox(width: 16),
        // 记录信息
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(exercise.name, style: TextStyle(fontWeight: FontWeight.bold)),
              Text('${_formatDateTime(record.date)}${record.caloriesBurned}卡路里'),
              if (record.notes.isNotEmpty)
                Text(record.notes, style: TextStyle(fontStyle: FontStyle.italic), maxLines: 1),
            ],
          ),
        ),
        // 状态和评分
        Column(
          children: [
            Container(
              padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: _getStatusColor(record.status),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(_getStatusName(record.status), 
                          style: TextStyle(color: Colors.white, fontSize: 10)),
            ),
            if (record.rating > 0)
              Row(
                mainAxisSize: MainAxisSize.min,
                children: List.generate(5, (index) => Icon(
                  index < record.rating ? Icons.star : Icons.star_border,
                  size: 12, color: Colors.amber,
                )),
              ),
          ],
        ),
      ],
    ),
  );
}

5. 搜索与筛选功能

应用提供强大的搜索和筛选功能,帮助用户快速找到所需的训练计划。

搜索功能实现
Widget _buildSearchAndFilter() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        // 搜索框
        TextField(
          decoration: const InputDecoration(
            hintText: '搜索训练计划...',
            prefixIcon: Icon(Icons.search),
            border: OutlineInputBorder(),
          ),
          onChanged: (value) => setState(() => _searchQuery = value),
        ),
        const SizedBox(height: 12),
        // 筛选选项
        Row(
          children: [
            Expanded(child: _buildFilterChip('类型', _getTypeFilterName(), _showTypeFilter)),
            Expanded(child: _buildFilterChip('难度', _getLevelFilterName(), _showLevelFilter)),
            Expanded(child: _buildFilterChip('排序', _getSortName(_sortBy), _showSortOptions)),
          ],
        ),
      ],
    ),
  );
}
数据筛选逻辑
List<TrainingPlan> _getFilteredPlans() {
  var filtered = _trainingPlans.where((plan) {
    // 搜索过滤
    if (_searchQuery.isNotEmpty) {
      final query = _searchQuery.toLowerCase();
      if (!plan.name.toLowerCase().contains(query) &&
          !plan.description.toLowerCase().contains(query)) {
        return false;
      }
    }
    
    // 类型过滤
    if (_selectedTypes.isNotEmpty) {
      if (!_selectedTypes.contains(plan.type)) {
        return false;
      }
    }
    
    // 难度过滤
    if (_selectedLevels.isNotEmpty) {
      if (!_selectedLevels.contains(plan.level)) {
        return false;
      }
    }
    
    return true;
  }).toList();
  
  // 排序处理
  filtered.sort((a, b) {
    switch (_sortBy) {
      case SortBy.name:
        return _sortAscending ? a.name.compareTo(b.name) : b.name.compareTo(a.name);
      case SortBy.date:
        return _sortAscending ? a.createdDate.compareTo(b.createdDate) : b.createdDate.compareTo(a.createdDate);
      case SortBy.level:
        return _sortAscending ? a.level.index.compareTo(b.level.index) : b.level.index.compareTo(a.level.index);
      case SortBy.duration:
        return _sortAscending ? a.estimatedDuration.compareTo(b.estimatedDuration) : b.estimatedDuration.compareTo(a.estimatedDuration);
      case SortBy.calories:
        return _sortAscending ? a.estimatedCalories.compareTo(b.estimatedCalories) : b.estimatedCalories.compareTo(a.estimatedCalories);
    }
  });
  
  return filtered;
}
筛选对话框
void _showTypeFilter() {
  showDialog(
    context: context,
    builder: (context) {
      return StatefulBuilder(
        builder: (context, setState) {
          return AlertDialog(
            title: const Text('选择训练类型'),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              children: TrainingType.values.map((type) {
                return CheckboxListTile(
                  title: Text(_getTrainingTypeName(type)),
                  value: _selectedTypes.contains(type),
                  onChanged: (checked) {
                    setState(() {
                      if (checked == true) {
                        _selectedTypes.add(type);
                      } else {
                        _selectedTypes.remove(type);
                      }
                    });
                  },
                );
              }).toList(),
            ),
            actions: [
              TextButton(onPressed: () => setState(() => _selectedTypes.clear()), child: Text('清空')),
              TextButton(onPressed: () => Navigator.pop(context), child: Text('取消')),
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(context);
                  this.setState(() {});
                },
                child: Text('确定'),
              ),
            ],
          );
        },
      );
    },
  );
}

6. 设置管理功能

设置页面提供个性化配置和数据管理功能。

设置页面结构
Widget _buildSettingsPage() {
  return Column(
    children: [
      _buildSettingsHeader(),
      Expanded(
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            _buildTrainingSettings(),    // 训练设置
            _buildBodyDataSettings(),    // 身体数据
            _buildDataManagement(),      // 数据管理
            _buildAboutSection(),        // 关于应用
          ],
        ),
      ),
    ],
  );
}
训练设置
Widget _buildTrainingSettings() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('训练设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          ListTile(
            leading: const Icon(Icons.schedule),
            title: const Text('默认休息时间'),
            subtitle: const Text('60秒'),
            onTap: _showRestTimeSettings,
          ),
          ListTile(
            leading: const Icon(Icons.notifications),
            title: const Text('训练提醒'),
            subtitle: const Text('每日20:00'),
            onTap: _showReminderSettings,
          ),
          SwitchListTile(
            secondary: const Icon(Icons.vibration),
            title: const Text('振动反馈'),
            subtitle: const Text('完成动作时振动'),
            value: _vibrationEnabled,
            onChanged: (value) => setState(() => _vibrationEnabled = value),
          ),
        ],
      ),
    ),
  );
}
身体数据管理
Widget _buildBodyDataSettings() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('身体数据', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          ListTile(
            leading: const Icon(Icons.person),
            title: const Text('个人信息'),
            subtitle: const Text('身高、体重、年龄'),
            onTap: _showPersonalInfo,
          ),
          ListTile(
            leading: const Icon(Icons.track_changes),
            title: const Text('健身目标'),
            subtitle: const Text('减脂、增肌、塑形'),
            onTap: _showFitnessGoals,
          ),
          ListTile(
            leading: const Icon(Icons.monitor_weight),
            title: const Text('体重记录'),
            subtitle: const Text('追踪体重变化'),
            onTap: _showWeightTracking,
          ),
        ],
      ),
    ),
  );
}

UI组件设计

1. 导航栏设计

应用采用底部导航栏设计,提供四个主要功能模块的快速切换。

NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) => setState(() => _selectedIndex = index),
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.today_outlined),
      selectedIcon: Icon(Icons.today),
      label: '今日训练',
    ),
    NavigationDestination(
      icon: Icon(Icons.fitness_center_outlined),
      selectedIcon: Icon(Icons.fitness_center),
      label: '训练计划',
    ),
    NavigationDestination(
      icon: Icon(Icons.trending_up_outlined),
      selectedIcon: Icon(Icons.trending_up),
      label: '训练进度',
    ),
    NavigationDestination(
      icon: Icon(Icons.settings_outlined),
      selectedIcon: Icon(Icons.settings),
      label: '设置',
    ),
  ],
)

2. 卡片组件设计

统计卡片
Widget _buildStatCard(String title, String value, IconData icon, Color color, String unit) {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: color.withValues(alpha: 0.3)),
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color, size: 32),
        const SizedBox(height: 8),
        Text(value, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color)),
        Text(unit, style: TextStyle(fontSize: 12, color: color)),
        const SizedBox(height: 4),
        Text(title, style: TextStyle(fontSize: 12, color: Colors.grey.shade600), textAlign: TextAlign.center),
      ],
    ),
  );
}
训练计划卡片
Widget _buildPlanSummary(TrainingPlan plan) {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.orange.shade50,
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: Colors.orange.shade200),
    ),
    child: Row(
      children: [
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            color: Colors.orange.shade100,
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(_getTrainingTypeIcon(plan.type), size: 30, color: Colors.orange.shade600),
        ),
        const SizedBox(width: 16),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(plan.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              Text('${plan.estimatedDuration}分钟 • ${plan.estimatedCalories}卡路里'),
              Text(_getTrainingLevelName(plan.level), 
                   style: TextStyle(fontSize: 12, color: Colors.orange.shade600)),
            ],
          ),
        ),
        ElevatedButton(
          onPressed: () => _startWorkout(plan),
          style: ElevatedButton.styleFrom(backgroundColor: Colors.orange, foregroundColor: Colors.white),
          child: const Text('开始训练'),
        ),
      ],
    ),
  );
}

3. 状态指示器

训练状态指示器
Widget _buildStatusIndicator(TrainingStatus status) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(
      color: _getStatusColor(status),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Text(
      _getStatusName(status),
      style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.w500),
    ),
  );
}
难度等级指示器
Widget _buildLevelChip(TrainingLevel level) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
    decoration: BoxDecoration(
      color: _getTrainingLevelColor(level).withValues(alpha: 0.2),
      borderRadius: BorderRadius.circular(8),
    ),
    child: Text(
      _getTrainingLevelName(level),
      style: TextStyle(fontSize: 10, color: _getTrainingLevelColor(level)),
    ),
  );
}

4. 动画效果

页面切换动画
class _FitnessTrainingHomePageState extends State<FitnessTrainingHomePage>
    with TickerProviderStateMixin {
  late AnimationController _fadeController;
  late AnimationController _scaleController;
  
  
  void initState() {
    super.initState();
    _fadeController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _scaleController = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );
    _fadeController.forward();
  }
  
  Widget _buildAnimatedPage(Widget child) {
    return FadeTransition(
      opacity: _fadeController,
      child: ScaleTransition(
        scale: Tween<double>(begin: 0.95, end: 1.0).animate(
          CurvedAnimation(parent: _scaleController, curve: Curves.easeOut),
        ),
        child: child,
      ),
    );
  }
}
进度条动画
Widget _buildProgressIndicator(double progress) {
  return TweenAnimationBuilder<double>(
    duration: const Duration(milliseconds: 500),
    tween: Tween<double>(begin: 0.0, end: progress),
    builder: (context, value, child) {
      return LinearProgressIndicator(
        value: value,
        backgroundColor: Colors.grey.shade300,
        valueColor: AlwaysStoppedAnimation<Color>(Colors.orange),
      );
    },
  );
}

工具方法实现

1. 日期时间处理

日期格式化
String _formatDate(DateTime date) {
  final now = DateTime.now();
  final today = DateTime(now.year, now.month, now.day);
  final targetDate = DateTime(date.year, date.month, date.day);

  if (targetDate == today) {
    return '今天';
  } else if (targetDate == today.subtract(const Duration(days: 1))) {
    return '昨天';
  } else if (targetDate == today.add(const Duration(days: 1))) {
    return '明天';
  } else {
    return '${date.month}${date.day}日';
  }
}

String _formatDateTime(DateTime dateTime) {
  return '${_formatDate(dateTime)} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
时间计算
bool _isToday(DateTime date) {
  final now = DateTime.now();
  return date.year == now.year && date.month == now.month && date.day == now.day;
}

Duration _getWorkoutDuration(TrainingRecord record) {
  if (record.endTime != null) {
    return record.endTime!.difference(record.startTime);
  }
  return Duration.zero;
}

int _getWeekOfYear(DateTime date) {
  final firstDayOfYear = DateTime(date.year, 1, 1);
  final daysSinceFirstDay = date.difference(firstDayOfYear).inDays;
  return (daysSinceFirstDay / 7).ceil();
}

2. 状态管理工具

状态颜色映射
Color _getStatusColor(TrainingStatus status) {
  switch (status) {
    case TrainingStatus.planned:
      return Colors.blue;
    case TrainingStatus.inProgress:
      return Colors.orange;
    case TrainingStatus.completed:
      return Colors.green;
    case TrainingStatus.skipped:
      return Colors.grey;
    case TrainingStatus.failed:
      return Colors.red;
  }
}

String _getStatusName(TrainingStatus status) {
  switch (status) {
    case TrainingStatus.planned:
      return '计划中';
    case TrainingStatus.inProgress:
      return '进行中';
    case TrainingStatus.completed:
      return '已完成';
    case TrainingStatus.skipped:
      return '跳过';
    case TrainingStatus.failed:
      return '失败';
  }
}
训练类型管理
String _getTrainingTypeName(TrainingType type) {
  const typeNames = {
    TrainingType.strength: '力量训练',
    TrainingType.cardio: '有氧训练',
    TrainingType.flexibility: '柔韧性',
    TrainingType.mixed: '混合训练',
    TrainingType.hiit: 'HIIT',
    TrainingType.yoga: '瑜伽',
    TrainingType.pilates: '普拉提',
  };
  return typeNames[type] ?? '未知';
}

Color _getTrainingTypeColor(TrainingType type) {
  const typeColors = {
    TrainingType.strength: Colors.red,
    TrainingType.cardio: Colors.blue,
    TrainingType.flexibility: Colors.green,
    TrainingType.mixed: Colors.orange,
    TrainingType.hiit: Colors.purple,
    TrainingType.yoga: Colors.teal,
    TrainingType.pilates: Colors.pink,
  };
  return typeColors[type] ?? Colors.grey;
}

IconData _getTrainingTypeIcon(TrainingType type) {
  const typeIcons = {
    TrainingType.strength: Icons.fitness_center,
    TrainingType.cardio: Icons.directions_run,
    TrainingType.flexibility: Icons.self_improvement,
    TrainingType.mixed: Icons.sports_gymnastics,
    TrainingType.hiit: Icons.flash_on,
    TrainingType.yoga: Icons.spa,
    TrainingType.pilates: Icons.accessibility_new,
  };
  return typeIcons[type] ?? Icons.fitness_center;
}

3. 数据验证工具

输入验证
class ValidationUtils {
  static String? validatePlanName(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入计划名称';
    }
    if (value.trim().length < 2) {
      return '计划名称至少需要2个字符';
    }
    return null;
  }
  
  static String? validateDuration(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入时长';
    }
    final duration = int.tryParse(value.trim());
    if (duration == null || duration <= 0) {
      return '请输入有效的时长';
    }
    if (duration > 300) {
      return '时长不能超过300分钟';
    }
    return null;
  }
  
  static String? validateCalories(String? value) {
    if (value != null && value.trim().isNotEmpty) {
      final calories = int.tryParse(value.trim());
      if (calories == null || calories < 0) {
        return '请输入有效的卡路里数值';
      }
      if (calories > 2000) {
        return '卡路里数值过大';
      }
    }
    return null;
  }
  
  static String? validateWeight(String? value) {
    if (value != null && value.trim().isNotEmpty) {
      final weight = double.tryParse(value.trim());
      if (weight == null || weight < 0) {
        return '请输入有效的重量';
      }
      if (weight > 500) {
        return '重量数值过大';
      }
    }
    return null;
  }
}
数据完整性检查
bool _isPlanDataComplete(TrainingPlan plan) {
  return plan.name.isNotEmpty &&
         plan.description.isNotEmpty &&
         plan.exercises.isNotEmpty &&
         plan.estimatedDuration > 0;
}

bool _isRecordValid(TrainingRecord record) {
  return record.exerciseId.isNotEmpty &&
         record.date.isBefore(DateTime.now().add(const Duration(days: 1))) &&
         record.caloriesBurned >= 0;
}

bool _isExerciseDataComplete(Exercise exercise) {
  return exercise.name.isNotEmpty &&
         exercise.description.isNotEmpty &&
         exercise.sets > 0 &&
         (exercise.reps > 0 || exercise.duration > 0);
}

4. 数据处理工具

统计计算
class FitnessStatistics {
  static double calculateBMI(double weight, double height) {
    if (height <= 0) return 0.0;
    return weight / (height * height);
  }
  
  static int calculateCaloriesBurned(Exercise exercise, int completedReps, int completedSets) {
    return exercise.caloriesPerRep * completedReps * completedSets;
  }
  
  static double calculateAverageRating(List<TrainingRecord> records) {
    final ratedRecords = records.where((r) => r.rating > 0).toList();
    if (ratedRecords.isEmpty) return 0.0;
    
    final totalRating = ratedRecords.fold(0.0, (sum, r) => sum + r.rating);
    return totalRating / ratedRecords.length;
  }
  
  static Map<TrainingType, int> getTrainingTypeDistribution(List<TrainingPlan> plans) {
    final distribution = <TrainingType, int>{};
    for (final type in TrainingType.values) {
      distribution[type] = plans.where((plan) => plan.type == type).length;
    }
    return distribution;
  }
  
  static List<TrainingPlan> getMostActiveWeekPlans(List<TrainingRecord> records) {
    final weeklyActivity = <String, int>{};
    
    for (final record in records) {
      if (record.status == TrainingStatus.completed) {
        final weekKey = '${record.date.year}-W${_getWeekOfYear(record.date)}';
        weeklyActivity[weekKey] = (weeklyActivity[weekKey] ?? 0) + 1;
      }
    }
    
    // 返回最活跃周的计划(这里简化处理)
    return [];
  }
}
数据排序
class SortingUtils {
  static List<TrainingPlan> sortPlans(List<TrainingPlan> plans, SortBy sortBy, bool ascending) {
    final sorted = List<TrainingPlan>.from(plans);
    
    sorted.sort((a, b) {
      int comparison;
      switch (sortBy) {
        case SortBy.name:
          comparison = a.name.compareTo(b.name);
          break;
        case SortBy.date:
          comparison = a.createdDate.compareTo(b.createdDate);
          break;
        case SortBy.level:
          comparison = a.level.index.compareTo(b.level.index);
          break;
        case SortBy.type:
          comparison = a.type.index.compareTo(b.type.index);
          break;
        case SortBy.duration:
          comparison = a.estimatedDuration.compareTo(b.estimatedDuration);
          break;
        case SortBy.calories:
          comparison = a.estimatedCalories.compareTo(b.estimatedCalories);
          break;
      }
      return ascending ? comparison : -comparison;
    });
    
    return sorted;
  }
  
  static List<TrainingRecord> sortRecords(List<TrainingRecord> records, bool byDateDescending) {
    final sorted = List<TrainingRecord>.from(records);
    sorted.sort((a, b) => byDateDescending 
        ? b.date.compareTo(a.date) 
        : a.date.compareTo(b.date));
    return sorted;
  }
}

5. 本地存储工具

数据序列化
class DataSerializer {
  static Map<String, dynamic> planToJson(TrainingPlan plan) {
    return {
      'id': plan.id,
      'name': plan.name,
      'description': plan.description,
      'level': plan.level.index,
      'type': plan.type.index,
      'durationWeeks': plan.durationWeeks,
      'sessionsPerWeek': plan.sessionsPerWeek,
      'targetMuscles': plan.targetMuscles,
      'exercises': plan.exercises.map((e) => exerciseToJson(e)).toList(),
      'imageUrl': plan.imageUrl,
      'createdDate': plan.createdDate.toIso8601String(),
      'isActive': plan.isActive,
      'notes': plan.notes,
      'estimatedCalories': plan.estimatedCalories,
      'estimatedDuration': plan.estimatedDuration,
    };
  }
  
  static TrainingPlan planFromJson(Map<String, dynamic> json) {
    return TrainingPlan(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      level: TrainingLevel.values[json['level']],
      type: TrainingType.values[json['type']],
      durationWeeks: json['durationWeeks'],
      sessionsPerWeek: json['sessionsPerWeek'],
      targetMuscles: List<String>.from(json['targetMuscles']),
      exercises: (json['exercises'] as List)
          .map((e) => exerciseFromJson(e))
          .toList(),
      imageUrl: json['imageUrl'] ?? '',
      createdDate: DateTime.parse(json['createdDate']),
      isActive: json['isActive'] ?? true,
      notes: json['notes'] ?? '',
      estimatedCalories: json['estimatedCalories'] ?? 0,
      estimatedDuration: json['estimatedDuration'] ?? 0,
    );
  }
  
  static Map<String, dynamic> exerciseToJson(Exercise exercise) {
    return {
      'id': exercise.id,
      'name': exercise.name,
      'description': exercise.description,
      'type': exercise.type.index,
      'primaryMuscle': exercise.primaryMuscle.index,
      'secondaryMuscles': exercise.secondaryMuscles.map((m) => m.index).toList(),
      'equipment': exercise.equipment.index,
      'difficulty': exercise.difficulty.index,
      'sets': exercise.sets,
      'reps': exercise.reps,
      'restSeconds': exercise.restSeconds,
      'weight': exercise.weight,
      'duration': exercise.duration,
      'instructions': exercise.instructions,
      'tips': exercise.tips,
      'videoUrl': exercise.videoUrl,
      'imageUrl': exercise.imageUrl,
      'caloriesPerRep': exercise.caloriesPerRep,
    };
  }
  
  static Exercise exerciseFromJson(Map<String, dynamic> json) {
    return Exercise(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      type: ExerciseType.values[json['type']],
      primaryMuscle: MuscleGroup.values[json['primaryMuscle']],
      secondaryMuscles: (json['secondaryMuscles'] as List)
          .map((m) => MuscleGroup.values[m])
          .toList(),
      equipment: EquipmentType.values[json['equipment']],
      difficulty: DifficultyLevel.values[json['difficulty']],
      sets: json['sets'],
      reps: json['reps'],
      restSeconds: json['restSeconds'],
      weight: json['weight']?.toDouble() ?? 0.0,
      duration: json['duration'],
      instructions: json['instructions'] ?? '',
      tips: List<String>.from(json['tips'] ?? []),
      videoUrl: json['videoUrl'] ?? '',
      imageUrl: json['imageUrl'] ?? '',
      caloriesPerRep: json['caloriesPerRep'] ?? 1,
    );
  }
  
  static Map<String, dynamic> recordToJson(TrainingRecord record) {
    return {
      'id': record.id,
      'planId': record.planId,
      'exerciseId': record.exerciseId,
      'date': record.date.toIso8601String(),
      'completedSets': record.completedSets,
      'completedReps': record.completedReps,
      'actualWeight': record.actualWeight,
      'actualDuration': record.actualDuration,
      'caloriesBurned': record.caloriesBurned,
      'rating': record.rating,
      'notes': record.notes,
      'status': record.status.index,
      'startTime': record.startTime.toIso8601String(),
      'endTime': record.endTime?.toIso8601String(),
    };
  }
  
  static TrainingRecord recordFromJson(Map<String, dynamic> json) {
    return TrainingRecord(
      id: json['id'],
      planId: json['planId'],
      exerciseId: json['exerciseId'],
      date: DateTime.parse(json['date']),
      completedSets: json['completedSets'] ?? 0,
      completedReps: json['completedReps'] ?? 0,
      actualWeight: json['actualWeight']?.toDouble() ?? 0.0,
      actualDuration: json['actualDuration'] ?? 0,
      caloriesBurned: json['caloriesBurned'] ?? 0,
      rating: json['rating']?.toDouble() ?? 0.0,
      notes: json['notes'] ?? '',
      status: TrainingStatus.values[json['status']],
      startTime: DateTime.parse(json['startTime']),
      endTime: json['endTime'] != null ? DateTime.parse(json['endTime']) : null,
    );
  }
}

6. 健身计算工具

健身指标计算
class FitnessCalculator {
  // 计算最大心率
  static int calculateMaxHeartRate(int age) {
    return 220 - age;
  }
  
  // 计算目标心率区间
  static Map<String, int> calculateTargetHeartRate(int age) {
    final maxHR = calculateMaxHeartRate(age);
    return {
      'fatBurn_min': (maxHR * 0.6).round(),    // 燃脂区间下限
      'fatBurn_max': (maxHR * 0.7).round(),    // 燃脂区间上限
      'cardio_min': (maxHR * 0.7).round(),     // 有氧区间下限
      'cardio_max': (maxHR * 0.85).round(),    // 有氧区间上限
      'anaerobic_min': (maxHR * 0.85).round(), // 无氧区间下限
      'anaerobic_max': maxHR,                  // 无氧区间上限
    };
  }
  
  // 计算一次性最大重量(1RM)
  static double calculateOneRepMax(double weight, int reps) {
    if (reps == 1) return weight;
    // 使用Brzycki公式
    return weight * (36 / (37 - reps));
  }
  
  // 计算训练强度百分比
  static double calculateIntensityPercentage(double currentWeight, double oneRepMax) {
    if (oneRepMax == 0) return 0.0;
    return (currentWeight / oneRepMax) * 100;
  }
  
  // 计算休息时间建议
  static int calculateRestTime(TrainingType type, DifficultyLevel difficulty) {
    switch (type) {
      case TrainingType.strength:
        switch (difficulty) {
          case DifficultyLevel.easy:
            return 60;
          case DifficultyLevel.medium:
            return 90;
          case DifficultyLevel.hard:
            return 120;
          case DifficultyLevel.extreme:
            return 180;
        }
      case TrainingType.hiit:
        return 30;
      case TrainingType.cardio:
        return 0; // 连续进行
      default:
        return 60;
    }
  }
  
  // 计算训练量(Volume)
  static int calculateTrainingVolume(List<Exercise> exercises) {
    return exercises.fold(0, (sum, exercise) {
      return sum + (exercise.sets * exercise.reps);
    });
  }
  
  // 计算训练密度
  static double calculateTrainingDensity(int totalVolume, int totalTimeMinutes) {
    if (totalTimeMinutes == 0) return 0.0;
    return totalVolume / totalTimeMinutes;
  }
}

功能扩展建议

1. 智能训练推荐系统

AI训练计划生成
class AITrainingPlanner {
  static TrainingPlan generatePersonalizedPlan({
    required int age,
    required double weight,
    required double height,
    required TrainingLevel level,
    required List<String> goals,
    required List<EquipmentType> availableEquipment,
    required int availableDaysPerWeek,
    required int sessionDurationMinutes,
  }) {
    // 基于用户数据生成个性化训练计划
    final bmi = weight / (height * height);
    final exercises = <Exercise>[];
    
    // 根据目标选择练习
    if (goals.contains('减脂')) {
      exercises.addAll(_getCardioExercises(availableEquipment, level));
      exercises.addAll(_getHIITExercises(availableEquipment, level));
    }
    
    if (goals.contains('增肌')) {
      exercises.addAll(_getStrengthExercises(availableEquipment, level));
    }
    
    if (goals.contains('塑形')) {
      exercises.addAll(_getFunctionalExercises(availableEquipment, level));
    }
    
    return TrainingPlan(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      name: 'AI定制计划',
      description: '基于您的身体数据和目标定制的专属训练计划',
      level: level,
      type: _determineTrainingType(goals),
      durationWeeks: _calculateOptimalDuration(level, goals),
      sessionsPerWeek: availableDaysPerWeek,
      targetMuscles: _getTargetMuscles(goals),
      exercises: exercises,
      createdDate: DateTime.now(),
      estimatedCalories: _calculateEstimatedCalories(exercises, weight),
      estimatedDuration: sessionDurationMinutes,
    );
  }
  
  static List<Exercise> _getCardioExercises(List<EquipmentType> equipment, TrainingLevel level) {
    // 根据器械和水平返回有氧练习
    return [];
  }
  
  static TrainingType _determineTrainingType(List<String> goals) {
    if (goals.length > 1) return TrainingType.mixed;
    if (goals.contains('减脂')) return TrainingType.cardio;
    if (goals.contains('增肌')) return TrainingType.strength;
    return TrainingType.mixed;
  }
}
智能进度调整
class ProgressiveOverload {
  static TrainingPlan adjustPlanDifficulty(
    TrainingPlan currentPlan,
    List<TrainingRecord> recentRecords,
  ) {
    final averageRating = recentRecords.fold(0.0, (sum, r) => sum + r.rating) / recentRecords.length;
    final completionRate = recentRecords.where((r) => r.status == TrainingStatus.completed).length / recentRecords.length;
    
    if (averageRating >= 4.0 && completionRate >= 0.8) {
      // 增加难度
      return _increaseDifficulty(currentPlan);
    } else if (averageRating <= 2.0 || completionRate <= 0.5) {
      // 降低难度
      return _decreaseDifficulty(currentPlan);
    }
    
    return currentPlan;
  }
  
  static TrainingPlan _increaseDifficulty(TrainingPlan plan) {
    final adjustedExercises = plan.exercises.map((exercise) {
      if (exercise.type == ExerciseType.strength) {
        return exercise.copyWith(
          weight: exercise.weight * 1.05, // 增加5%重量
          reps: exercise.reps + 1,         // 增加1次
        );
      } else if (exercise.type == ExerciseType.cardio) {
        return exercise.copyWith(
          duration: (exercise.duration * 1.1).round(), // 增加10%时长
        );
      }
      return exercise;
    }).toList();
    
    return plan.copyWith(exercises: adjustedExercises);
  }
}

2. 社交健身功能

好友系统
class FitnessUser {
  final String id;
  final String name;
  final String avatar;
  final int level;
  final List<String> achievements;
  final FitnessStats stats;
  
  const FitnessUser({
    required this.id,
    required this.name,
    required this.avatar,
    required this.level,
    required this.achievements,
    required this.stats,
  });
}

class FitnessStats {
  final int totalWorkouts;
  final int totalCalories;
  final int currentStreak;
  final int longestStreak;
  final double averageRating;
  
  const FitnessStats({
    required this.totalWorkouts,
    required this.totalCalories,
    required this.currentStreak,
    required this.longestStreak,
    required this.averageRating,
  });
}

class SocialFitnessService {
  static Future<List<FitnessUser>> getFriends(String userId) async {
    // 获取好友列表
    return [];
  }
  
  static Future<void> shareWorkout(TrainingRecord record) async {
    // 分享训练记录到社交平台
  }
  
  static Future<List<Challenge>> getActiveChallenges() async {
    // 获取活跃的健身挑战
    return [];
  }
}
挑战系统
class Challenge {
  final String id;
  final String name;
  final String description;
  final ChallengeType type;
  final DateTime startDate;
  final DateTime endDate;
  final Map<String, dynamic> target;
  final List<String> participants;
  final String reward;
  
  const Challenge({
    required this.id,
    required this.name,
    required this.description,
    required this.type,
    required this.startDate,
    required this.endDate,
    required this.target,
    required this.participants,
    required this.reward,
  });
}

enum ChallengeType {
  dailyWorkout,    // 每日训练
  weeklyCalories,  // 周卡路里
  monthlyStreak,   // 月连续
  specificExercise, // 特定练习
}

class ChallengeManager {
  static List<Challenge> generateWeeklyChallenges() {
    return [
      Challenge(
        id: '1',
        name: '7天训练挑战',
        description: '连续7天完成训练',
        type: ChallengeType.dailyWorkout,
        startDate: DateTime.now(),
        endDate: DateTime.now().add(const Duration(days: 7)),
        target: {'days': 7},
        participants: [],
        reward: '健身达人徽章',
      ),
      Challenge(
        id: '2',
        name: '周燃脂挑战',
        description: '本周消耗2000卡路里',
        type: ChallengeType.weeklyCalories,
        startDate: DateTime.now(),
        endDate: DateTime.now().add(const Duration(days: 7)),
        target: {'calories': 2000},
        participants: [],
        reward: '燃脂王者称号',
      ),
    ];
  }
}

3. 健康数据集成

健康数据同步
// 添加依赖:health
class HealthDataIntegration {
  static Future<bool> requestPermissions() async {
    final health = Health();
    final types = [
      HealthDataType.STEPS,
      HealthDataType.HEART_RATE,
      HealthDataType.WEIGHT,
      HealthDataType.ACTIVE_ENERGY_BURNED,
    ];
    
    return await health.requestAuthorization(types);
  }
  
  static Future<Map<String, dynamic>> getTodayHealthData() async {
    final health = Health();
    final now = DateTime.now();
    final startOfDay = DateTime(now.year, now.month, now.day);
    
    final steps = await health.getTotalStepsInInterval(startOfDay, now);
    final heartRate = await health.getHealthDataFromTypes(
      [HealthDataType.HEART_RATE],
      startOfDay,
      now,
    );
    
    return {
      'steps': steps ?? 0,
      'heartRate': heartRate.isNotEmpty ? heartRate.last.value : 0,
      'date': now,
    };
  }
  
  static Future<void> writeWorkoutData(TrainingRecord record) async {
    final health = Health();
    
    await health.writeHealthData(
      record.caloriesBurned.toDouble(),
      HealthDataType.ACTIVE_ENERGY_BURNED,
      record.startTime,
      record.endTime ?? DateTime.now(),
    );
  }
}
可穿戴设备集成
class WearableDeviceManager {
  static Future<bool> connectToDevice(String deviceType) async {
    switch (deviceType) {
      case 'apple_watch':
        return await _connectToAppleWatch();
      case 'fitbit':
        return await _connectToFitbit();
      case 'garmin':
        return await _connectToGarmin();
      default:
        return false;
    }
  }
  
  static Future<Map<String, dynamic>> getRealtimeData() async {
    // 获取实时心率、步数等数据
    return {
      'heartRate': 75,
      'steps': 8500,
      'calories': 320,
      'timestamp': DateTime.now(),
    };
  }
  
  static Future<bool> _connectToAppleWatch() async {
    // Apple Watch连接逻辑
    return true;
  }
}

4. 营养管理集成

营养计划
class NutritionPlan {
  final String id;
  final String name;
  final int dailyCalories;
  final Map<String, double> macroRatios; // 蛋白质、碳水、脂肪比例
  final List<MealPlan> meals;
  final DateTime createdDate;
  
  const NutritionPlan({
    required this.id,
    required this.name,
    required this.dailyCalories,
    required this.macroRatios,
    required this.meals,
    required this.createdDate,
  });
}

class MealPlan {
  final String name;
  final int calories;
  final Map<String, double> nutrients;
  final List<String> foods;
  final String mealTime;
  
  const MealPlan({
    required this.name,
    required this.calories,
    required this.nutrients,
    required this.foods,
    required this.mealTime,
  });
}

class NutritionCalculator {
  static NutritionPlan generateNutritionPlan({
    required double weight,
    required double height,
    required int age,
    required String gender,
    required String goal,
    required TrainingLevel activityLevel,
  }) {
    // 计算基础代谢率(BMR)
    final bmr = _calculateBMR(weight, height, age, gender);
    
    // 计算总日消耗(TDEE)
    final tdee = _calculateTDEE(bmr, activityLevel);
    
    // 根据目标调整卡路里
    final targetCalories = _adjustCaloriesForGoal(tdee, goal);
    
    // 计算宏量营养素比例
    final macroRatios = _calculateMacroRatios(goal);
    
    return NutritionPlan(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      name: '个性化营养计划',
      dailyCalories: targetCalories.round(),
      macroRatios: macroRatios,
      meals: _generateMealPlans(targetCalories, macroRatios),
      createdDate: DateTime.now(),
    );
  }
  
  static double _calculateBMR(double weight, double height, int age, String gender) {
    // 使用Mifflin-St Jeor公式
    if (gender.toLowerCase() == 'male') {
      return 10 * weight + 6.25 * height - 5 * age + 5;
    } else {
      return 10 * weight + 6.25 * height - 5 * age - 161;
    }
  }
  
  static double _calculateTDEE(double bmr, TrainingLevel activityLevel) {
    switch (activityLevel) {
      case TrainingLevel.beginner:
        return bmr * 1.2; // 久坐
      case TrainingLevel.intermediate:
        return bmr * 1.55; // 中等活动
      case TrainingLevel.advanced:
        return bmr * 1.725; // 高活动
      case TrainingLevel.expert:
        return bmr * 1.9; // 极高活动
    }
  }
}

5. 虚拟教练功能

AI语音指导
// 添加依赖:flutter_tts, speech_to_text
class VirtualCoach {
  static final FlutterTts _tts = FlutterTts();
  static final SpeechToText _speech = SpeechToText();
  
  static Future<void> initializeCoach() async {
    await _tts.setLanguage('zh-CN');
    await _tts.setSpeechRate(0.8);
    await _tts.setVolume(0.8);
    await _tts.setPitch(1.0);
  }
  
  static Future<void> provideExerciseGuidance(Exercise exercise) async {
    final guidance = _generateGuidanceText(exercise);
    await _tts.speak(guidance);
  }
  
  static Future<void> countdownRest(int seconds) async {
    for (int i = seconds; i > 0; i--) {
      if (i <= 10) {
        await _tts.speak('$i');
        await Future.delayed(const Duration(seconds: 1));
      } else {
        await Future.delayed(const Duration(seconds: 1));
      }
    }
    await _tts.speak('休息结束,开始下一组');
  }
  
  static Future<void> motivate(String message) async {
    final motivationalMessages = [
      '你做得很棒!继续保持!',
      '坚持就是胜利!',
      '每一次训练都让你更强!',
      '相信自己,你可以的!',
      '今天的汗水是明天的成果!',
    ];
    
    final randomMessage = motivationalMessages[
      DateTime.now().millisecond % motivationalMessages.length
    ];
    
    await _tts.speak(message.isEmpty ? randomMessage : message);
  }
  
  static String _generateGuidanceText(Exercise exercise) {
    return '现在开始${exercise.name},'
           '完成${exercise.sets}组,每组${exercise.reps}次。'
           '${exercise.instructions}';
  }
  
  static Future<String?> listenToCommand() async {
    final available = await _speech.initialize();
    if (!available) return null;
    
    final completer = Completer<String?>();
    
    _speech.listen(
      onResult: (result) {
        if (result.finalResult) {
          completer.complete(result.recognizedWords);
        }
      },
      localeId: 'zh_CN',
    );
    
    return completer.future;
  }
}
动作识别与纠正
// 添加依赖:camera, tflite
class PoseDetection {
  static Future<bool> initializeModel() async {
    try {
      await Tflite.loadModel(
        model: 'assets/models/pose_detection.tflite',
        labels: 'assets/models/pose_labels.txt',
      );
      return true;
    } catch (e) {
      return false;
    }
  }
  
  static Future<List<PoseKeypoint>> detectPose(String imagePath) async {
    final recognitions = await Tflite.runPoseNetOnImage(
      path: imagePath,
      numResults: 1,
    );
    
    return recognitions?.map((r) => PoseKeypoint.fromMap(r)).toList() ?? [];
  }
  
  static ExerciseForm analyzePushupForm(List<PoseKeypoint> keypoints) {
    // 分析俯卧撑动作标准性
    final leftShoulder = keypoints.firstWhere((k) => k.part == 'leftShoulder');
    final rightShoulder = keypoints.firstWhere((k) => k.part == 'rightShoulder');
    final leftElbow = keypoints.firstWhere((k) => k.part == 'leftElbow');
    final rightElbow = keypoints.firstWhere((k) => k.part == 'rightElbow');
    
    // 检查肩膀是否水平
    final shoulderLevel = (leftShoulder.y - rightShoulder.y).abs();
    final isShoulderLevel = shoulderLevel < 0.05;
    
    // 检查手肘角度
    final elbowAngle = _calculateAngle(leftShoulder, leftElbow, keypoints.firstWhere((k) => k.part == 'leftWrist'));
    final isElbowAngleCorrect = elbowAngle >= 45 && elbowAngle <= 90;
    
    return ExerciseForm(
      isCorrect: isShoulderLevel && isElbowAngleCorrect,
      feedback: _generateFormFeedback(isShoulderLevel, isElbowAngleCorrect),
      score: _calculateFormScore(isShoulderLevel, isElbowAngleCorrect),
    );
  }
  
  static double _calculateAngle(PoseKeypoint point1, PoseKeypoint point2, PoseKeypoint point3) {
    // 计算三点之间的角度
    final vector1 = [point1.x - point2.x, point1.y - point2.y];
    final vector2 = [point3.x - point2.x, point3.y - point2.y];
    
    final dotProduct = vector1[0] * vector2[0] + vector1[1] * vector2[1];
    final magnitude1 = sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1]);
    final magnitude2 = sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1]);
    
    final cosAngle = dotProduct / (magnitude1 * magnitude2);
    return acos(cosAngle) * 180 / pi;
  }
}

class PoseKeypoint {
  final String part;
  final double x;
  final double y;
  final double confidence;
  
  const PoseKeypoint({
    required this.part,
    required this.x,
    required this.y,
    required this.confidence,
  });
  
  factory PoseKeypoint.fromMap(Map<String, dynamic> map) {
    return PoseKeypoint(
      part: map['part'],
      x: map['x'],
      y: map['y'],
      confidence: map['confidence'],
    );
  }
}

class ExerciseForm {
  final bool isCorrect;
  final String feedback;
  final double score;
  
  const ExerciseForm({
    required this.isCorrect,
    required this.feedback,
    required this.score,
  });
}

6. 数据分析与可视化

高级图表展示
// 添加依赖:fl_chart
class FitnessCharts {
  static Widget buildProgressChart(List<TrainingRecord> records) {
    return LineChart(
      LineChartData(
        gridData: FlGridData(show: true),
        titlesData: FlTitlesData(show: true),
        borderData: FlBorderData(show: true),
        lineBarsData: [
          LineChartBarData(
            spots: _generateProgressSpots(records),
            isCurved: true,
            color: Colors.orange,
            barWidth: 3,
            dotData: FlDotData(show: true),
            belowBarData: BarAreaData(
              show: true,
              color: Colors.orange.withValues(alpha: 0.3),
            ),
          ),
        ],
      ),
    );
  }
  
  static Widget buildCalorieChart(List<TrainingRecord> records) {
    return BarChart(
      BarChartData(
        alignment: BarChartAlignment.spaceAround,
        maxY: _getMaxCalories(records),
        barTouchData: BarTouchData(enabled: true),
        titlesData: FlTitlesData(show: true),
        borderData: FlBorderData(show: false),
        barGroups: _generateCalorieBarGroups(records),
      ),
    );
  }
  
  static Widget buildMuscleGroupChart(List<TrainingRecord> records) {
    return PieChart(
      PieChartData(
        sections: _generateMuscleGroupSections(records),
        centerSpaceRadius: 40,
        sectionsSpace: 2,
      ),
    );
  }
  
  static List<FlSpot> _generateProgressSpots(List<TrainingRecord> records) {
    final weeklyData = <int, double>{};
    
    for (int i = 0; i < 12; i++) {
      final weekStart = DateTime.now().subtract(Duration(days: (11 - i) * 7));
      final weekEnd = weekStart.add(const Duration(days: 7));
      
      final weekRecords = records.where((r) =>
          r.date.isAfter(weekStart) && r.date.isBefore(weekEnd) &&
          r.status == TrainingStatus.completed).toList();
      
      final totalCalories = weekRecords.fold(0, (sum, r) => sum + r.caloriesBurned);
      weeklyData[i] = totalCalories.toDouble();
    }
    
    return weeklyData.entries.map((e) => FlSpot(e.key.toDouble(), e.value)).toList();
  }
}

r2[0] * vector2[0] + vector2[1] * vector2[1]);

final cosAngle = dotProduct / (magnitude1 * magnitude2);
return acos(cosAngle) * 180 / pi;

}

static String _generateFormFeedback(bool isShoulderLevel, bool isElbowAngleCorrect) {
final feedback = [];

if (!isShoulderLevel) {
  feedback.add('保持肩膀水平');
}

if (!isElbowAngleCorrect) {
  feedback.add('调整手肘角度至45-90度');
}

if (feedback.isEmpty) {
  return '动作标准,继续保持!';
}

return '建议:${feedback.join(',')}';

}

static double _calculateFormScore(bool isShoulderLevel, bool isElbowAngleCorrect) {
double score = 0.0;
if (isShoulderLevel) score += 50.0;
if (isElbowAngleCorrect) score += 50.0;
return score;
}
}

class PoseKeypoint {
final String part;
final double x;
final double y;
final double confidence;

const PoseKeypoint({
required this.part,
required this.x,
required this.y,
required this.confidence,
});

factory PoseKeypoint.fromMap(Map<String, dynamic> map) {
return PoseKeypoint(
part: map[‘part’],
x: map[‘x’]?.toDouble() ?? 0.0,
y: map[‘y’]?.toDouble() ?? 0.0,
confidence: map[‘confidence’]?.toDouble() ?? 0.0,
);
}
}

class ExerciseForm {
final bool isCorrect;
final String feedback;
final double score;

const ExerciseForm({
required this.isCorrect,
required this.feedback,
required this.score,
});
}


### 6. 数据可视化增强

#### 训练进度图表

```dart
// 添加依赖:fl_chart
class ProgressCharts {
  static Widget buildCaloriesChart(List<TrainingRecord> records) {
    final data = _prepareCaloriesData(records);
    
    return LineChart(
      LineChartData(
        gridData: FlGridData(show: true),
        titlesData: FlTitlesData(
          leftTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              getTitlesWidget: (value, meta) => Text('${value.toInt()}'),
            ),
          ),
          bottomTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              getTitlesWidget: (value, meta) => Text(_formatChartDate(value)),
            ),
          ),
        ),
        borderData: FlBorderData(show: true),
        lineBarsData: [
          LineChartBarData(
            spots: data,
            isCurved: true,
            color: Colors.orange,
            barWidth: 3,
            dotData: FlDotData(show: true),
          ),
        ],
      ),
    );
  }
  
  static Widget buildWorkoutFrequencyChart(List<TrainingRecord> records) {
    final data = _prepareFrequencyData(records);
    
    return BarChart(
      BarChartData(
        alignment: BarChartAlignment.spaceAround,
        maxY: data.map((e) => e.y).reduce((a, b) => a > b ? a : b) * 1.2,
        barTouchData: BarTouchData(enabled: true),
        titlesData: FlTitlesData(
          leftTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              getTitlesWidget: (value, meta) => Text('${value.toInt()}'),
            ),
          ),
          bottomTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              getTitlesWidget: (value, meta) => Text(_getWeekdayName(value.toInt())),
            ),
          ),
        ),
        borderData: FlBorderData(show: false),
        barGroups: data.asMap().entries.map((entry) {
          return BarChartGroupData(
            x: entry.key,
            barRods: [
              BarChartRodData(
                toY: entry.value.y,
                color: Colors.orange,
                width: 20,
                borderRadius: BorderRadius.circular(4),
              ),
            ],
          );
        }).toList(),
      ),
    );
  }
  
  static Widget buildMuscleGroupDistribution(List<TrainingPlan> plans) {
    final data = _prepareMuscleGroupData(plans);
    
    return PieChart(
      PieChartData(
        sections: data.asMap().entries.map((entry) {
          final index = entry.key;
          final data = entry.value;
          
          return PieChartSectionData(
            color: _getMuscleGroupColor(data.muscleGroup),
            value: data.percentage,
            title: '${data.percentage.toStringAsFixed(1)}%',
            radius: 100,
            titleStyle: const TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          );
        }).toList(),
        sectionsSpace: 2,
        centerSpaceRadius: 40,
      ),
    );
  }
  
  static List<FlSpot> _prepareCaloriesData(List<TrainingRecord> records) {
    final dailyCalories = <DateTime, int>{};
    
    for (final record in records) {
      final date = DateTime(record.date.year, record.date.month, record.date.day);
      dailyCalories[date] = (dailyCalories[date] ?? 0) + record.caloriesBurned;
    }
    
    final sortedEntries = dailyCalories.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));
    
    return sortedEntries.asMap().entries.map((entry) {
      return FlSpot(entry.key.toDouble(), entry.value.value.toDouble());
    }).toList();
  }
  
  static List<FlSpot> _prepareFrequencyData(List<TrainingRecord> records) {
    final weekdayCount = List.filled(7, 0);
    
    for (final record in records) {
      if (record.status == TrainingStatus.completed) {
        weekdayCount[record.date.weekday - 1]++;
      }
    }
    
    return weekdayCount.asMap().entries.map((entry) {
      return FlSpot(entry.key.toDouble(), entry.value.toDouble());
    }).toList();
  }
  
  static List<MuscleGroupData> _prepareMuscleGroupData(List<TrainingPlan> plans) {
    final muscleGroupCount = <MuscleGroup, int>{};
    
    for (final plan in plans) {
      for (final exercise in plan.exercises) {
        muscleGroupCount[exercise.primaryMuscle] = 
            (muscleGroupCount[exercise.primaryMuscle] ?? 0) + 1;
      }
    }
    
    final total = muscleGroupCount.values.fold(0, (sum, count) => sum + count);
    
    return muscleGroupCount.entries.map((entry) {
      return MuscleGroupData(
        muscleGroup: entry.key,
        count: entry.value,
        percentage: (entry.value / total) * 100,
      );
    }).toList();
  }
  
  static String _formatChartDate(double value) {
    final date = DateTime.now().subtract(Duration(days: (30 - value).toInt()));
    return '${date.month}/${date.day}';
  }
  
  static String _getWeekdayName(int weekday) {
    const weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
    return weekdays[weekday];
  }
}

class MuscleGroupData {
  final MuscleGroup muscleGroup;
  final int count;
  final double percentage;
  
  const MuscleGroupData({
    required this.muscleGroup,
    required this.count,
    required this.percentage,
  });
}

性能优化策略

1. 内存管理优化

图片缓存优化
class ImageCacheManager {
  static final Map<String, Uint8List> _cache = {};
  static const int _maxCacheSize = 50; // 最大缓存50张图片
  
  static Future<ImageProvider> getImage(String url) async {
    if (_cache.containsKey(url)) {
      return MemoryImage(_cache[url]!);
    }
    
    try {
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
        final bytes = response.bodyBytes;
        
        // 如果缓存已满,移除最旧的图片
        if (_cache.length >= _maxCacheSize) {
          final firstKey = _cache.keys.first;
          _cache.remove(firstKey);
        }
        
        _cache[url] = bytes;
        return MemoryImage(bytes);
      }
    } catch (e) {
      // 返回默认图片
      return const AssetImage('assets/images/default_exercise.png');
    }
    
    return const AssetImage('assets/images/default_exercise.png');
  }
  
  static void clearCache() {
    _cache.clear();
  }
  
  static int getCacheSize() {
    return _cache.length;
  }
}
列表性能优化
class OptimizedTrainingPlanList extends StatefulWidget {
  final List<TrainingPlan> plans;
  final Function(TrainingPlan) onPlanTap;
  
  const OptimizedTrainingPlanList({
    super.key,
    required this.plans,
    required this.onPlanTap,
  });
  
  
  State<OptimizedTrainingPlanList> createState() => _OptimizedTrainingPlanListState();
}

class _OptimizedTrainingPlanListState extends State<OptimizedTrainingPlanList> {
  final ScrollController _scrollController = ScrollController();
  
  
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: widget.plans.length,
      // 使用 itemExtent 提高性能
      itemExtent: 120.0,
      // 缓存范围优化
      cacheExtent: 500.0,
      itemBuilder: (context, index) {
        final plan = widget.plans[index];
        
        return RepaintBoundary(
          child: TrainingPlanCard(
            plan: plan,
            onTap: () => widget.onPlanTap(plan),
          ),
        );
      },
    );
  }
}

class TrainingPlanCard extends StatelessWidget {
  final TrainingPlan plan;
  final VoidCallback onTap;
  
  const TrainingPlanCard({
    super.key,
    required this.plan,
    required this.onTap,
  });
  
  
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              // 使用 Hero 动画优化页面切换
              Hero(
                tag: 'plan_${plan.id}',
                child: Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: _getTrainingTypeColor(plan.type).withValues(alpha: 0.2),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    _getTrainingTypeIcon(plan.type),
                    size: 30,
                    color: _getTrainingTypeColor(plan.type),
                  ),
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      plan.name,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '${plan.estimatedDuration}分钟 • ${plan.estimatedCalories}卡路里',
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.grey.shade600,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      _getTrainingLevelName(plan.level),
                      style: TextStyle(
                        fontSize: 12,
                        color: _getTrainingTypeColor(plan.type),
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2. 数据库性能优化

批量操作优化
class DatabaseOptimizer {
  static Future<void> batchInsertRecords(List<TrainingRecord> records) async {
    final db = await DatabaseHelper.database;
    
    await db.transaction((txn) async {
      final batch = txn.batch();
      
      for (final record in records) {
        batch.insert(
          'training_records',
          DataSerializer.recordToJson(record),
          conflictAlgorithm: ConflictAlgorithm.replace,
        );
      }
      
      await batch.commit(noResult: true);
    });
  }
  
  static Future<List<TrainingRecord>> getRecordsWithPagination({
    required int offset,
    required int limit,
    String? planId,
  }) async {
    final db = await DatabaseHelper.database;
    
    String whereClause = '';
    List<dynamic> whereArgs = [];
    
    if (planId != null) {
      whereClause = 'WHERE plan_id = ?';
      whereArgs.add(planId);
    }
    
    final List<Map<String, dynamic>> maps = await db.rawQuery('''
      SELECT * FROM training_records 
      $whereClause
      ORDER BY date DESC 
      LIMIT ? OFFSET ?
    ''', [...whereArgs, limit, offset]);
    
    return maps.map((map) => DataSerializer.recordFromJson(map)).toList();
  }
  
  static Future<void> createIndexes() async {
    final db = await DatabaseHelper.database;
    
    // 为常用查询字段创建索引
    await db.execute('CREATE INDEX IF NOT EXISTS idx_records_date ON training_records(date)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_records_plan_id ON training_records(plan_id)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_records_status ON training_records(status)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_plans_active ON training_plans(is_active)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_plans_type ON training_plans(type)');
  }
  
  static Future<void> optimizeDatabase() async {
    final db = await DatabaseHelper.database;
    
    // 分析表统计信息
    await db.execute('ANALYZE');
    
    // 清理碎片
    await db.execute('VACUUM');
  }
}

3. 网络请求优化

请求缓存和重试机制
class NetworkOptimizer {
  static final Map<String, CachedResponse> _cache = {};
  static const Duration _cacheExpiry = Duration(minutes: 5);
  
  static Future<http.Response> optimizedGet(
    String url, {
    Duration? cacheExpiry,
    int maxRetries = 3,
  }) async {
    final cacheKey = url;
    final cached = _cache[cacheKey];
    
    // 检查缓存
    if (cached != null && 
        DateTime.now().difference(cached.timestamp) < (cacheExpiry ?? _cacheExpiry)) {
      return http.Response(cached.body, cached.statusCode);
    }
    
    // 执行请求,带重试机制
    for (int attempt = 0; attempt < maxRetries; attempt++) {
      try {
        final response = await http.get(
          Uri.parse(url),
          headers: {
            'User-Agent': 'FitnessTrainingApp/1.0',
            'Accept': 'application/json',
          },
        ).timeout(const Duration(seconds: 10));
        
        // 缓存成功响应
        if (response.statusCode == 200) {
          _cache[cacheKey] = CachedResponse(
            body: response.body,
            statusCode: response.statusCode,
            timestamp: DateTime.now(),
          );
        }
        
        return response;
      } catch (e) {
        if (attempt == maxRetries - 1) {
          rethrow;
        }
        
        // 指数退避
        await Future.delayed(Duration(milliseconds: 1000 * (attempt + 1)));
      }
    }
    
    throw Exception('Max retries exceeded');
  }
  
  static void clearCache() {
    _cache.clear();
  }
  
  static void removeExpiredCache() {
    final now = DateTime.now();
    _cache.removeWhere((key, value) => 
        now.difference(value.timestamp) > _cacheExpiry);
  }
}

class CachedResponse {
  final String body;
  final int statusCode;
  final DateTime timestamp;
  
  const CachedResponse({
    required this.body,
    required this.statusCode,
    required this.timestamp,
  });
}

4. UI渲染优化

动画性能优化
class OptimizedAnimations {
  static Widget buildFadeInAnimation({
    required Widget child,
    Duration duration = const Duration(milliseconds: 300),
    Curve curve = Curves.easeInOut,
  }) {
    return TweenAnimationBuilder<double>(
      duration: duration,
      tween: Tween<double>(begin: 0.0, end: 1.0),
      curve: curve,
      builder: (context, value, child) {
        return Opacity(
          opacity: value,
          child: Transform.translate(
            offset: Offset(0, 20 * (1 - value)),
            child: child,
          ),
        );
      },
      child: child,
    );
  }
  
  static Widget buildSlideAnimation({
    required Widget child,
    required AnimationController controller,
    Offset begin = const Offset(1.0, 0.0),
    Offset end = Offset.zero,
  }) {
    final animation = Tween<Offset>(
      begin: begin,
      end: end,
    ).animate(CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOutCubic,
    ));
    
    return SlideTransition(
      position: animation,
      child: child,
    );
  }
  
  static Widget buildScaleAnimation({
    required Widget child,
    required AnimationController controller,
    double begin = 0.0,
    double end = 1.0,
  }) {
    final animation = Tween<double>(
      begin: begin,
      end: end,
    ).animate(CurvedAnimation(
      parent: controller,
      curve: Curves.elasticOut,
    ));
    
    return ScaleTransition(
      scale: animation,
      child: child,
    );
  }
}

测试指南

1. 单元测试

数据模型测试
// test/models/training_plan_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:fitness_training/models/training_plan.dart';

void main() {
  group('TrainingPlan Tests', () {
    late TrainingPlan testPlan;
    
    setUp(() {
      testPlan = TrainingPlan(
        id: 'test_1',
        name: '测试计划',
        description: '这是一个测试计划',
        level: TrainingLevel.beginner,
        type: TrainingType.strength,
        durationWeeks: 4,
        sessionsPerWeek: 3,
        targetMuscles: ['胸部', '手臂'],
        exercises: [],
        createdDate: DateTime.now(),
        estimatedCalories: 200,
        estimatedDuration: 30,
      );
    });
    
    test('should create TrainingPlan with correct properties', () {
      expect(testPlan.id, 'test_1');
      expect(testPlan.name, '测试计划');
      expect(testPlan.level, TrainingLevel.beginner);
      expect(testPlan.type, TrainingType.strength);
      expect(testPlan.isActive, true);
    });
    
    test('should copy TrainingPlan with new properties', () {
      final copiedPlan = testPlan.copyWith(
        name: '新计划名称',
        level: TrainingLevel.intermediate,
      );
      
      expect(copiedPlan.name, '新计划名称');
      expect(copiedPlan.level, TrainingLevel.intermediate);
      expect(copiedPlan.id, testPlan.id); // 未改变的属性应保持不变
    });
    
    test('should validate plan data completeness', () {
      expect(_isPlanDataComplete(testPlan), true);
      
      final incompletePlan = testPlan.copyWith(name: '');
      expect(_isPlanDataComplete(incompletePlan), false);
    });
  });
}
工具方法测试
// test/utils/fitness_calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:fitness_training/utils/fitness_calculator.dart';

void main() {
  group('FitnessCalculator Tests', () {
    test('should calculate max heart rate correctly', () {
      expect(FitnessCalculator.calculateMaxHeartRate(25), 195);
      expect(FitnessCalculator.calculateMaxHeartRate(40), 180);
      expect(FitnessCalculator.calculateMaxHeartRate(60), 160);
    });
    
    test('should calculate target heart rate zones', () {
      final zones = FitnessCalculator.calculateTargetHeartRate(30);
      
      expect(zones['fatBurn_min'], 114); // (220-30) * 0.6
      expect(zones['fatBurn_max'], 133); // (220-30) * 0.7
      expect(zones['cardio_min'], 133);  // (220-30) * 0.7
      expect(zones['cardio_max'], 162);  // (220-30) * 0.85
    });
    
    test('should calculate one rep max correctly', () {
      expect(FitnessCalculator.calculateOneRepMax(100, 1), 100);
      expect(FitnessCalculator.calculateOneRepMax(80, 5), closeTo(90.0, 1.0));
      expect(FitnessCalculator.calculateOneRepMax(60, 10), closeTo(80.0, 2.0));
    });
    
    test('should calculate BMI correctly', () {
      expect(FitnessStatistics.calculateBMI(70, 1.75), closeTo(22.86, 0.01));
      expect(FitnessStatistics.calculateBMI(80, 1.80), closeTo(24.69, 0.01));
      expect(FitnessStatistics.calculateBMI(0, 1.75), 0.0);
    });
  });
}

2. Widget测试

训练计划卡片测试
// test/widgets/training_plan_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fitness_training/widgets/training_plan_card.dart';
import 'package:fitness_training/models/training_plan.dart';

void main() {
  group('TrainingPlanCard Widget Tests', () {
    late TrainingPlan testPlan;
    
    setUp(() {
      testPlan = TrainingPlan(
        id: 'test_1',
        name: '测试计划',
        description: '测试描述',
        level: TrainingLevel.beginner,
        type: TrainingType.strength,
        durationWeeks: 4,
        sessionsPerWeek: 3,
        targetMuscles: ['胸部'],
        exercises: [],
        createdDate: DateTime.now(),
        estimatedCalories: 200,
        estimatedDuration: 30,
      );
    });
    
    testWidgets('should display plan information correctly', (tester) async {
      bool tapped = false;
      
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: TrainingPlanCard(
              plan: testPlan,
              onTap: () => tapped = true,
            ),
          ),
        ),
      );
      
      // 验证计划名称显示
      expect(find.text('测试计划'), findsOneWidget);
      
      // 验证时长和卡路里显示
      expect(find.text('30分钟 • 200卡路里'), findsOneWidget);
      
      // 验证难度等级显示
      expect(find.text('初级'), findsOneWidget);
      
      // 测试点击事件
      await tester.tap(find.byType(TrainingPlanCard));
      await tester.pump();
      
      expect(tapped, true);
    });
    
    testWidgets('should show correct training type icon', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: TrainingPlanCard(
              plan: testPlan,
              onTap: () {},
            ),
          ),
        ),
      );
      
      // 验证力量训练图标
      expect(find.byIcon(Icons.fitness_center), findsOneWidget);
    });
  });
}

3. 集成测试

完整训练流程测试
// integration_test/training_flow_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:fitness_training/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('Training Flow Integration Tests', () {
    testWidgets('complete training session flow', (tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // 1. 验证应用启动
      expect(find.text('今日训练'), findsOneWidget);
      
      // 2. 导航到训练计划页面
      await tester.tap(find.text('训练计划'));
      await tester.pumpAndSettle();
      
      // 3. 创建新的训练计划
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();
      
      // 填写计划信息
      await tester.enterText(find.byKey(const Key('plan_name_field')), '集成测试计划');
      await tester.enterText(find.byKey(const Key('plan_description_field')), '这是集成测试创建的计划');
      
      // 保存计划
      await tester.tap(find.text('保存'));
      await tester.pumpAndSettle();
      
      // 4. 验证计划创建成功
      expect(find.text('集成测试计划'), findsOneWidget);
      
      // 5. 开始训练
      await tester.tap(find.text('开始训练'));
      await tester.pumpAndSettle();
      
      // 6. 完成训练
      await tester.tap(find.text('完成练习'));
      await tester.pumpAndSettle();
      
      // 7. 验证训练记录
      await tester.tap(find.text('训练进度'));
      await tester.pumpAndSettle();
      
      expect(find.text('已完成'), findsOneWidget);
    });
    
    testWidgets('search and filter functionality', (tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // 导航到训练计划页面
      await tester.tap(find.text('训练计划'));
      await tester.pumpAndSettle();
      
      // 测试搜索功能
      await tester.enterText(find.byKey(const Key('search_field')), '新手');
      await tester.pumpAndSettle();
      
      // 验证搜索结果
      expect(find.text('新手入门计划'), findsOneWidget);
      
      // 测试筛选功能
      await tester.tap(find.text('类型'));
      await tester.pumpAndSettle();
      
      await tester.tap(find.text('力量训练'));
      await tester.tap(find.text('确定'));
      await tester.pumpAndSettle();
      
      // 验证筛选结果
      expect(find.text('力量提升计划'), findsOneWidget);
    });
  });
}

4. 性能测试

内存和渲染性能测试
// test/performance/performance_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fitness_training/pages/training_plans_page.dart';
import 'package:fitness_training/models/training_plan.dart';

void main() {
  group('Performance Tests', () {
    testWidgets('large list rendering performance', (tester) async {
      // 创建大量测试数据
      final plans = List.generate(1000, (index) => TrainingPlan(
        id: 'plan_$index',
        name: '计划 $index',
        description: '描述 $index',
        level: TrainingLevel.values[index % TrainingLevel.values.length],
        type: TrainingType.values[index % TrainingType.values.length],
        durationWeeks: 4,
        sessionsPerWeek: 3,
        targetMuscles: ['肌群$index'],
        exercises: [],
        createdDate: DateTime.now(),
        estimatedCalories: 200 + index,
        estimatedDuration: 30 + index % 60,
      ));
      
      // 测量渲染时间
      final stopwatch = Stopwatch()..start();
      
      await tester.pumpWidget(
        MaterialApp(
          home: TrainingPlansPage(plans: plans),
        ),
      );
      
      await tester.pumpAndSettle();
      stopwatch.stop();
      
      // 验证渲染时间在合理范围内(< 1秒)
      expect(stopwatch.elapsedMilliseconds, lessThan(1000));
      
      // 验证列表可以正常滚动
      await tester.fling(find.byType(ListView), const Offset(0, -500), 1000);
      await tester.pumpAndSettle();
      
      // 验证内存使用情况
      final binding = TestWidgetsFlutterBinding.ensureInitialized();
      final memoryUsage = binding.defaultBinaryMessenger;
      
      // 这里可以添加更详细的内存监控逻辑
    });
    
    testWidgets('animation performance test', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: OptimizedAnimations.buildFadeInAnimation(
              child: Container(
                width: 200,
                height: 200,
                color: Colors.blue,
              ),
            ),
          ),
        ),
      );
      
      // 测量动画帧率
      final frames = <Duration>[];
      
      await tester.binding.addTime(const Duration(milliseconds: 16));
      frames.add(const Duration(milliseconds: 16));
      
      for (int i = 0; i < 60; i++) {
        await tester.pump(const Duration(milliseconds: 16));
        frames.add(Duration(milliseconds: 16 * (i + 2)));
      }
      
      // 验证动画流畅度(60fps)
      expect(frames.length, 61);
    });
  });
}

部署指南

1. Android部署

构建配置
# android/app/build.gradle
android {
    compileSdkVersion 34
    
    defaultConfig {
        applicationId "com.example.fitness_training"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName "1.0.0"
        
        multiDexEnabled true
    }
    
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
权限配置
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <!-- 存储权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    <!-- 健康数据权限 -->
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
    <uses-permission android:name="android.permission.BODY_SENSORS" />
    
    <!-- 相机权限(用于动作识别) -->
    <uses-permission android:name="android.permission.CAMERA" />
    
    <!-- 音频权限(用于语音指导) -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
    <!-- 振动权限 -->
    <uses-permission android:name="android.permission.VIBRATE" />
    
    <!-- 通知权限 -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    
    <application
        android:label="健身训练计划"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:theme="@style/LaunchTheme"
        android:exported="true"
        android:usesCleartextTraffic="true">
        
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme" />
                
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        
        <!-- 后台服务配置 -->
        <service
            android:name=".TrainingReminderService"
            android:enabled="true"
            android:exported="false" />
            
        <!-- 广播接收器 -->
        <receiver
            android:name=".BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>
构建脚本
#!/bin/bash
# scripts/build_android.sh

echo "开始构建Android应用..."

# 清理之前的构建
flutter clean

# 获取依赖
flutter pub get

# 运行代码生成
flutter packages pub run build_runner build --delete-conflicting-outputs

# 运行测试
flutter test

# 构建APK
flutter build apk --release --split-per-abi

# 构建AAB(用于Google Play)
flutter build appbundle --release

echo "Android构建完成!"
echo "APK文件位置: build/app/outputs/flutter-apk/"
echo "AAB文件位置: build/app/outputs/bundle/release/"

2. iOS部署

项目配置
<!-- ios/Runner/Info.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>健身训练计划</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>fitness_training</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$(FLUTTER_BUILD_NAME)</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    
    <!-- 权限描述 -->
    <key>NSCameraUsageDescription</key>
    <string>应用需要访问相机来进行动作识别和训练指导</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>应用需要访问麦克风来提供语音指导功能</string>
    <key>NSMotionUsageDescription</key>
    <string>应用需要访问运动数据来追踪您的健身活动</string>
    <key>NSHealthShareUsageDescription</key>
    <string>应用需要读取健康数据来提供个性化的训练建议</string>
    <key>NSHealthUpdateUsageDescription</key>
    <string>应用需要写入健康数据来记录您的训练成果</string>
    
    <!-- 后台模式 -->
    <key>UIBackgroundModes</key>
    <array>
        <string>background-processing</string>
        <string>background-fetch</string>
    </array>
</dict>
</plist>
构建脚本
#!/bin/bash
# scripts/build_ios.sh

echo "开始构建iOS应用..."

# 清理之前的构建
flutter clean

# 获取依赖
flutter pub get

# 运行代码生成
flutter packages pub run build_runner build --delete-conflicting-outputs

# 运行测试
flutter test

# 构建iOS应用
flutter build ios --release --no-codesign

echo "iOS构建完成!"
echo "请在Xcode中打开项目进行签名和发布"

3. Web部署

构建配置
<!-- web/index.html -->
<!DOCTYPE html>
<html>
<head>
  <base href="$FLUTTER_BASE_HREF">
  
  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="专业的健身训练计划管理应用">
  <meta name="keywords" content="健身,训练,计划,运动,健康">
  <meta name="author" content="Fitness Training Team">
  
  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="健身训练计划">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">
  
  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>
  
  <title>健身训练计划</title>
  <link rel="manifest" href="manifest.json">
  
  <style>
    body {
      margin: 0;
      padding: 0;
      background-color: #f5f5f5;
      font-family: 'Roboto', sans-serif;
    }
    
    .loading {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      flex-direction: column;
    }
    
    .loading-spinner {
      width: 40px;
      height: 40px;
      border: 4px solid #f3f3f3;
      border-top: 4px solid #ff9800;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  </style>
</head>
<body>
  <div id="loading" class="loading">
    <div class="loading-spinner"></div>
    <p>正在加载健身训练计划...</p>
  </div>
  
  <script>
    window.addEventListener('flutter-first-frame', function () {
      document.getElementById('loading').style.display = 'none';
    });
  </script>
  
  <script src="flutter.js" defer></script>
</body>
</html>
PWA配置
// web/manifest.json
{
  "name": "健身训练计划",
  "short_name": "健身计划",
  "description": "专业的健身训练计划管理应用",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#ff9800",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "icons/Icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "icons/Icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ],
  "categories": ["health", "fitness", "lifestyle"],
  "lang": "zh-CN",
  "scope": "/",
  "screenshots": [
    {
      "src": "screenshots/home.png",
      "sizes": "1080x1920",
      "type": "image/png"
    },
    {
      "src": "screenshots/plans.png",
      "sizes": "1080x1920",
      "type": "image/png"
    }
  ]
}
部署脚本
#!/bin/bash
# scripts/deploy_web.sh

echo "开始构建Web应用..."

# 清理之前的构建
flutter clean

# 获取依赖
flutter pub get

# 构建Web应用
flutter build web --release --web-renderer canvaskit

# 部署到服务器(示例:使用rsync)
if [ "$1" = "production" ]; then
    echo "部署到生产环境..."
    rsync -avz --delete build/web/ user@server:/var/www/fitness-training/
elif [ "$1" = "staging" ]; then
    echo "部署到测试环境..."
    rsync -avz --delete build/web/ user@staging-server:/var/www/fitness-training-staging/
else
    echo "Web构建完成!"
    echo "构建文件位置: build/web/"
    echo "使用 'flutter serve' 或部署到Web服务器"
fi

4. CI/CD配置

GitHub Actions配置
# .github/workflows/ci_cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run code generation
      run: flutter packages pub run build_runner build --delete-conflicting-outputs
      
    - name: Run tests
      run: flutter test --coverage
      
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: coverage/lcov.info

  build_android:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Setup Java
      uses: actions/setup-java@v3
      with:
        distribution: 'zulu'
        java-version: '11'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build APK
      run: flutter build apk --release
      
    - name: Upload APK
      uses: actions/upload-artifact@v3
      with:
        name: app-release.apk
        path: build/app/outputs/flutter-apk/app-release.apk

  build_ios:
    needs: test
    runs-on: macos-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build iOS
      run: flutter build ios --release --no-codesign
      
  build_web:
    needs: test
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build Web
      run: flutter build web --release
      
    - name: Deploy to GitHub Pages
      if: github.ref == 'refs/heads/main'
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./build/web

项目总结

健身训练计划应用是一个功能完整、设计精良的Flutter应用,展示了现代移动应用开发的最佳实践。通过本教程的学习,你将掌握:

技术收获

  1. Flutter框架深度应用:从基础Widget到复杂状态管理,全面掌握Flutter开发技能
  2. Material Design 3实践:现代化的UI设计语言应用,提供优秀的用户体验
  3. 数据架构设计:完整的数据模型设计,支持复杂的业务逻辑
  4. 性能优化技巧:内存管理、渲染优化、网络请求优化等实用技能
  5. 测试驱动开发:单元测试、Widget测试、集成测试的完整实践

业务价值

  1. 用户体验优先:直观的界面设计,流畅的交互体验
  2. 功能完整性:涵盖训练计划制定、执行、追踪的完整流程
  3. 数据驱动决策:详细的统计分析,帮助用户优化训练效果
  4. 个性化服务:基于用户数据的智能推荐和调整
  5. 扩展性设计:模块化架构,便于功能扩展和维护

开发经验

  1. 项目架构规划:清晰的代码结构,便于团队协作和维护
  2. 版本控制实践:Git工作流程,代码审查和持续集成
  3. 文档编写习惯:完整的技术文档,降低项目维护成本
  4. 用户反馈循环:基于用户需求的迭代开发流程
  5. 跨平台部署:Android、iOS、Web多平台发布经验

未来发展方向

  1. AI智能化:集成机器学习算法,提供更智能的训练建议
  2. 社交化功能:构建健身社区,增强用户粘性
  3. 硬件集成:支持更多可穿戴设备和健身器械
  4. 数据分析:深度挖掘用户数据,提供专业的健身指导
  5. 商业化探索:付费计划、教练服务、营养指导等增值服务

通过这个项目的开发,你不仅学会了Flutter技术栈的应用,更重要的是掌握了完整的产品开发流程。从需求分析到架构设计,从编码实现到测试部署,每个环节都体现了专业的软件开发素养。

希望这个健身训练计划应用能够成为你Flutter学习路上的重要里程碑,也期待你能够基于这个基础,开发出更多优秀的应用产品。

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

Logo

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

更多推荐