Flutter for OpenHarmony 身体健康状况记录App实战 - 运动分析实现

#

前言

运动分析页面帮助用户了解自己的运动习惯和效果。通过统计本周的运动数据,用户可以看到自己的运动时长、消耗热量、运动类型分布等信息,从而更好地规划运动计划。

这篇文章会讲解运动分析页面的实现,包括运动汇总卡片和运动类型分布两个核心组件。


页面整体结构

运动分析页面包含汇总数据卡片和运动类型分布两个主要部分。

dart 复制代码
class ExerciseAnalysisPage extends StatelessWidget {
  const ExerciseAnalysisPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFFAFAFC),
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading: IconButton(
          icon: Icon(Icons.arrow_back_ios_rounded, size: 20.w), 
          onPressed: () => Get.back()
        ),
        title: Text('运动分析', style: TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w600)),
        centerTitle: true,
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(20.w),
        child: Column(
          children: [
            _buildSummaryCard(),
            SizedBox(height: 20.h),
            _buildTypeStats(),
          ],
        ),
      ),
    );
  }
}

页面使用统一的浅灰色背景和透明 AppBar,和其他统计页面保持一致的视觉风格。


运动汇总卡片

汇总卡片使用青绿色渐变背景,展示本周运动时长和相关统计数据。

dart 复制代码
Widget _buildSummaryCard() {
  return Container(
    padding: EdgeInsets.all(24.w),
    decoration: BoxDecoration(
      gradient: const LinearGradient(
        colors: [Color(0xFF00C9A7), Color(0xFF4ECDC4)]
      ),
      borderRadius: BorderRadius.circular(24.r),
    ),
    child: Column(
      children: [
        Text('本周运动', style: TextStyle(fontSize: 13.sp, color: Colors.white70)),
        SizedBox(height: 8.h),
        Text('156 分钟', style: TextStyle(
          fontSize: 36.sp, 
          fontWeight: FontWeight.w700, 
          color: Colors.white
        )),
        SizedBox(height: 4.h),
        Text('目标 150 分钟 · 已达标', style: TextStyle(
          fontSize: 13.sp, 
          color: Colors.white70
        )),
        SizedBox(height: 16.h),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            _buildMiniStat('运动天数', '5天'),
            _buildMiniStat('消耗热量', '1,280kcal'),
            _buildMiniStat('总距离', '12.5km'),
          ],
        ),
      ],
    ),
  );
}

Widget _buildMiniStat(String label, String value) {
  return Column(
    children: [
      Text(value, style: TextStyle(
        fontSize: 15.sp, 
        fontWeight: FontWeight.w600, 
        color: Colors.white
      )),
      SizedBox(height: 2.h),
      Text(label, style: TextStyle(fontSize: 11.sp, color: Colors.white60)),
    ],
  );
}

渐变色从 #00C9A7#4ECDC4,是一个清新的青绿色系,和运动主题非常契合。本周运动时长使用 36sp 的大字号居中显示,下方显示目标和达成状态。

底部三个迷你统计项展示运动天数、消耗热量和总距离,用 spaceAround 让三个项目均匀分布。


运动目标达成判断

根据运动时长判断是否达成目标:

dart 复制代码
Map<String, dynamic> _getGoalStatus(int currentMinutes, int targetMinutes) {
  final percentage = currentMinutes / targetMinutes;
  
  if (percentage >= 1.0) {
    return {
      'text': '已达标',
      'emoji': '🎉',
      'color': const Color(0xFF00C9A7),
    };
  } else if (percentage >= 0.8) {
    return {
      'text': '即将达标',
      'emoji': '💪',
      'color': const Color(0xFFFFBE0B),
    };
  } else {
    return {
      'text': '继续加油',
      'emoji': '🏃',
      'color': const Color(0xFFFF6B6B),
    };
  }
}

达成 100% 显示"已达标",达成 80% 以上显示"即将达标",否则显示"继续加油"。不同状态配合不同的 emoji 和颜色,给用户直观的反馈。


运动类型分布

运动类型分布展示用户不同运动类型的时间占比,用进度条可视化。

dart 复制代码
Widget _buildTypeStats() {
  final types = [
    {'type': '跑步', 'time': '68min', 'percent': 0.44, 'color': const Color(0xFFFF6B6B)},
    {'type': '步行', 'time': '45min', 'percent': 0.29, 'color': const Color(0xFF4D96FF)},
    {'type': '游泳', 'time': '28min', 'percent': 0.18, 'color': const Color(0xFF00C9A7)},
    {'type': '其他', 'time': '15min', 'percent': 0.09, 'color': const Color(0xFFFFBE0B)},
  ];

  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white, 
      borderRadius: BorderRadius.circular(20.r)
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('运动类型分布', style: TextStyle(
          fontSize: 16.sp, 
          fontWeight: FontWeight.w600, 
          color: const Color(0xFF1A1A2E)
        )),
        SizedBox(height: 20.h),
        ...types.map((t) => Padding(
          padding: EdgeInsets.only(bottom: 16.h),
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(t['type'] as String, style: TextStyle(
                    fontSize: 14.sp, 
                    color: const Color(0xFF1A1A2E)
                  )),
                  Text(t['time'] as String, style: TextStyle(
                    fontSize: 14.sp, 
                    fontWeight: FontWeight.w600, 
                    color: t['color'] as Color
                  )),
                ],
              ),
              SizedBox(height: 8.h),
              ClipRRect(
                borderRadius: BorderRadius.circular(4.r),
                child: LinearProgressIndicator(
                  value: t['percent'] as double,
                  minHeight: 8.h,
                  backgroundColor: Colors.grey[100],
                  valueColor: AlwaysStoppedAnimation(t['color'] as Color),
                ),
              ),
            ],
          ),
        )),
      ],
    ),
  );
}

每种运动类型用不同颜色的进度条表示,进度条的长度对应时间占比。运动类型名称在左边,时间在右边,用对应的颜色显示。

ClipRRect 给进度条添加圆角,让视觉效果更柔和。LinearProgressIndicatorvalue 属性接受 0-1 之间的值,正好对应百分比。


运动类型颜色映射

为不同的运动类型分配固定的颜色:

dart 复制代码
Color _getExerciseColor(String type) {
  switch (type) {
    case '跑步': return const Color(0xFFFF6B6B);
    case '步行': return const Color(0xFF4D96FF);
    case '骑行': return const Color(0xFF00C9A7);
    case '游泳': return const Color(0xFF845EC2);
    case '瑜伽': return const Color(0xFFFFBE0B);
    case '健身': return const Color(0xFFFF9F43);
    default: return const Color(0xFF6C63FF);
  }
}

固定的颜色映射让用户能快速识别不同的运动类型,在整个应用中保持一致。


每日运动统计

可以添加一个每日运动统计的柱状图:

dart 复制代码
Widget _buildDailyStats() {
  final days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
  final minutes = [30, 0, 45, 32, 0, 49, 0];
  final maxMinutes = minutes.reduce((a, b) => a > b ? a : b);

  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(20.r),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('每日运动', style: TextStyle(
          fontSize: 16.sp,
          fontWeight: FontWeight.w600,
          color: const Color(0xFF1A1A2E),
        )),
        SizedBox(height: 20.h),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: List.generate(7, (i) => Column(
            children: [
              Container(
                width: 32.w,
                height: maxMinutes > 0 
                  ? (minutes[i] / maxMinutes * 100).h.clamp(4.h, 100.h)
                  : 4.h,
                decoration: BoxDecoration(
                  color: minutes[i] > 0 
                    ? const Color(0xFF00C9A7) 
                    : Colors.grey[200],
                  borderRadius: BorderRadius.circular(6.r),
                ),
              ),
              SizedBox(height: 8.h),
              Text(days[i], style: TextStyle(
                fontSize: 10.sp, 
                color: Colors.grey[500]
              )),
            ],
          )),
        ),
      ],
    ),
  );
}

柱状图展示每天的运动时长,有运动的日子用绿色,没运动的日子用灰色。这种可视化让用户一眼就能看出自己的运动规律。


热量消耗分析

可以添加热量消耗的详细分析:

dart 复制代码
Widget _buildCalorieAnalysis() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('热量消耗', style: TextStyle(
          fontSize: 14.sp,
          fontWeight: FontWeight.w500,
          color: const Color(0xFF1A1A2E),
        )),
        SizedBox(height: 12.h),
        Row(
          children: [
            _buildCalorieItem('本周', '1,280', 'kcal', const Color(0xFFFF6B6B)),
            SizedBox(width: 16.w),
            _buildCalorieItem('日均', '183', 'kcal', const Color(0xFFFFBE0B)),
          ],
        ),
        SizedBox(height: 12.h),
        Container(
          padding: EdgeInsets.all(10.w),
          decoration: BoxDecoration(
            color: const Color(0xFFFF6B6B).withOpacity(0.08),
            borderRadius: BorderRadius.circular(8.r),
          ),
          child: Row(
            children: [
              Icon(Icons.local_fire_department_rounded, size: 16.w, color: const Color(0xFFFF6B6B)),
              SizedBox(width: 8.w),
              Expanded(
                child: Text(
                  '相当于消耗了约0.18kg脂肪',
                  style: TextStyle(fontSize: 12.sp, color: const Color(0xFFFF6B6B)),
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

Widget _buildCalorieItem(String label, String value, String unit, Color color) {
  return Expanded(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(label, style: TextStyle(fontSize: 11.sp, color: Colors.grey[500])),
        SizedBox(height: 4.h),
        Row(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(value, style: TextStyle(
              fontSize: 20.sp,
              fontWeight: FontWeight.w700,
              color: color,
            )),
            Text(' $unit', style: TextStyle(fontSize: 11.sp, color: Colors.grey[400])),
          ],
        ),
      ],
    ),
  );
}

热量分析展示本周总消耗和日均消耗,并用一个有趣的换算(相当于消耗多少脂肪)让数据更直观。


运动建议

根据运动数据生成个性化建议:

dart 复制代码
List<String> _generateExerciseTips(int weeklyMinutes, int exerciseDays) {
  List<String> tips = [];
  
  if (weeklyMinutes >= 150) {
    tips.add('本周运动时长已达到推荐标准,继续保持!');
  } else {
    final remaining = 150 - weeklyMinutes;
    tips.add('距离本周目标还差$remaining分钟,加油!');
  }
  
  if (exerciseDays >= 5) {
    tips.add('运动频率很好,每周运动5天以上对健康很有益');
  } else if (exerciseDays >= 3) {
    tips.add('运动频率不错,建议增加到每周5天');
  } else {
    tips.add('建议增加运动频率,每周至少运动3天');
  }
  
  tips.add('运动后记得补充水分和适当休息');
  
  return tips;
}

建议内容根据运动时长和频率动态生成,给用户提供有针对性的指导。


与上周对比

展示和上周的运动数据对比:

dart 复制代码
Widget _buildWeekComparison() {
  return Container(
    padding: EdgeInsets.all(12.w),
    decoration: BoxDecoration(
      color: const Color(0xFF00C9A7).withOpacity(0.1),
      borderRadius: BorderRadius.circular(10.r),
    ),
    child: Row(
      children: [
        Icon(Icons.trending_up_rounded, size: 20.w, color: const Color(0xFF00C9A7)),
        SizedBox(width: 10.w),
        Expanded(
          child: Text(
            '比上周多运动了28分钟,消耗热量增加15%',
            style: TextStyle(fontSize: 13.sp, color: const Color(0xFF00C9A7)),
          ),
        ),
      ],
    ),
  );
}

对比数据用箭头图标表示变化方向,增加用绿色向上箭头,减少用红色向下箭头。


小结

运动分析页面通过青绿色渐变卡片展示汇总数据,用进度条可视化运动类型分布,再配合每日统计和热量分析,帮助用户全面了解自己的运动情况。

核心设计要点包括:目标达成状态用文字和 emoji 直观表达,运动类型用不同颜色区分,进度条长度对应时间占比。这些设计让用户能清楚地看到自己的运动习惯,有针对性地调整运动计划。


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

相关推荐
Tansmjs2 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
2401_841495642 小时前
【操作系统】进程的算法
python·算法·操作系统·进程·进程调度算法·进程同步与互斥算法·死锁处理算法
夏幻灵2 小时前
Java中的this关键字解析与应用
java·开发语言·python
LetsonH2 小时前
Swap 大小一键调整脚本
人工智能·python
子春一2 小时前
Flutter for OpenHarmony:用 Flutter 构建一个数字猜谜游戏:从零开始的交互式应用开发
javascript·flutter·游戏
半路_出家ren2 小时前
5.RSA和AES加密(python)
服务器·网络·python·https·aes·rsa·加密算法
SunnyRivers2 小时前
10分钟入门Python 异步编程
python·异步·asyncio·asyn·asynhttp
BlackWolfSky2 小时前
鸿蒙中级课程笔记3—ArkUI进阶6—ArkUI性能优化实践(长列表加载性能优化)
笔记·华为·harmonyos
大雷神2 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地--第14篇:数据分析与可视化
华为·数据分析·harmonyos