当前位置: 首页 > news >正文

【鸿蒙原生应用开发--ArkUI--013】Exercise-tracker 运动记录应用开发教程

Exercise-tracker 运动记录应用开发教程

项目介绍

项目背景

运动记录应用是一个帮助用户记录和追踪运动数据的健康管理工具。随着人们对健康生活方式的重视,运动已经成为日常生活的重要组成部分。然而,很多人在运动时缺乏系统的记录和分析,导致无法准确了解自己的运动状况和进步情况。

运动记录应用通过数字化的方式,帮助用户记录每次运动的类型、时长、消耗的卡路里等数据,并提供统计分析功能,让用户能够清晰地看到自己的运动轨迹和成果。这种可视化的反馈机制能够有效激励用户坚持运动,养成健康的生活习惯。

应用场景

  • 健身记录:记录每次运动的详细数据,包括运动类型、持续时间、消耗卡路里等。用户可以随时查看自己的运动历史。

  • 数据分析:分析运动习惯和趋势,了解哪些运动类型最常进行,哪些时间段运动最频繁。

  • 目标追踪:设定运动目标并追踪完成情况,如每周运动次数、每月运动时长等。

  • 健康监测:长期记录运动数据,为健康管理提供参考依据。

功能特性

  1. 运动记录:记录运动类型、时长、消耗卡路里和备注信息。

  2. 类型选择:支持多种运动类型,如跑步、游泳、骑行、健身、瑜伽等。

  3. 数据统计:统计本周运动次数、总时长和总消耗卡路里。

  4. 历史记录:查看历史运动记录,支持删除操作。

  5. 图标显示:根据运动类型显示相应的图标,直观易识别。

最终效果

应用采用绿色主题,象征着健康和活力。主界面包含:

  • 顶部标题栏和添加按钮
  • 本周统计卡片,显示运动次数、总时长和总消耗
  • 运动记录列表,显示每条记录的详情

技术栈

  • 开发框架:HarmonyOS NEXT (API 20+)
  • 编程语言:ArkTS
  • UI框架:ArkUI 声明式 UI
  • 核心组件:Column, Row, List, Button, TextInput, Select

知识点讲解

1. 数据统计

使用reduce方法进行数据统计,计算总和、平均值等。

// 计算本周统计数据privategetWeeklyStats(){consttoday=newDate()constweekAgo=newDate(today.getTime()-7*24*60*60*1000)// 过滤本周的记录constweeklyRecords=this.records.filter(record=>{constrecordDate=newDate(record.date)returnrecordDate>=weekAgo&&recordDate<=today})// 计算统计数据return{// 运动次数count:weeklyRecords.length,// 总时长(使用 reduce 累加)totalDuration:weeklyRecords.reduce((sum,record)=>sum+record.duration,0),// 总消耗卡路里totalCalories:weeklyRecords.reduce((sum,record)=>sum+record.calories,0)}}

2. 图标映射

使用对象映射实现图标选择,根据运动类型返回对应的图标。

// 定义运动类型图标映射privatereadonlytypeIcons:Record<string,string>={'跑步':'🏃','游泳':'🏊','骑行':'🚴','健身':'💪','瑜伽':'🧘','跳绳':'⚡','篮球':'🏀','羽毛球':'🏸','足球':'⚽','网球':'🎾'}// 获取运动类型图标privategetTypeIcon(type:string):string{returnthis.typeIcons[type]||'🏃'// 默认图标}

3. 日期处理

获取和格式化日期,用于记录和筛选运动数据。

// 获取今天的日期字符串privategetTodayString():string{consttoday=newDate()constyear=today.getFullYear()constmonth=(today.getMonth()+1).toString().padStart(2,'0')constday=today.getDate().toString().padStart(2,'0')return`${year}-${month}-${day}`}// 格式化日期显示privateformatDate(dateStr:string):string{constdate=newDate(dateStr)constmonth=(date.getMonth()+1).toString().padStart(2,'0')constday=date.getDate().toString().padStart(2,'0')return`${month}${day}`}// 计算相对日期privategetRelativeDate(dateStr:string):string{consttoday=this.getTodayString()if(dateStr===today)return'今天'constyesterday=newDate()yesterday.setDate(yesterday.getDate()-1)if(dateStr===this.formatDateToString(yesterday))return'昨天'returnthis.formatDate(dateStr)}

4. 表单处理

处理添加运动记录的表单数据,包括输入验证和数据格式化。

// 表单状态@StatenewType:string='跑步'@StatenewDuration:string='30'@StatenewCalories:string='200'@StatenewNotes:string=''// 添加运动记录privateaddRecord(){// 验证输入constduration=parseInt(this.newDuration)constcalories=parseInt(this.newCalories)if(isNaN(duration)||duration<=0){// 提示错误return}if(isNaN(calories)||calories<=0){// 提示错误return}// 创建新记录constnewRecord:ExerciseRecord={id:Date.now(),type:this.newType,duration:duration,calories:calories,date:this.getTodayString(),notes:this.newNotes.trim()}// 添加到列表this.records.unshift(newRecord)// 清空表单this.clearForm()// 关闭表单this.showAddRecord=false}// 清空表单privateclearForm(){this.newType='跑步'this.newDuration='30'this.newCalories='200'this.newNotes=''}

5. 列表渲染

使用 List 和 ForEach 渲染运动记录列表。

List(){ForEach(this.records,(record:ExerciseRecord)=>{ListItem(){Row(){// 运动类型图标Text(this.getTypeIcon(record.type)).fontSize(32).margin({right:12})// 记录信息Column(){Text(record.type).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#1e293b')Text(`${record.duration}分钟 ·${record.calories}千卡`).fontSize(12).fontColor('#64748b').margin({top:2})if(record.notes!==''){Text(record.notes).fontSize(12).fontColor('#9ca3af').margin({top:2}).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})}}.width('60%')// 日期和删除按钮Column(){Text(this.getRelativeDate(record.date)).fontSize(12).fontColor('#64748b')Button(){Text('×').fontSize(16).fontColor('#9ca3af')}.width(24).height(24).backgroundColor('transparent').margin({top:4}).onClick(()=>{this.deleteRecord(record.id)})}.width('20%').alignItems(HorizontalAlign.End)}.width('100%').padding(16).backgroundColor('#ffffff').borderRadius(12).margin({bottom:8})}})}.width('100%').layoutWeight(1)

6. 统计卡片

显示本周运动统计数据。

Column(){Text('本周统计').fontSize(16).fontColor('#64748b').margin({bottom:12})Row(){// 运动次数Column(){Text(`${this.getWeeklyStats().count}`).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#10b981')Text('次运动').fontSize(12).fontColor('#64748b')}.width('33%').alignItems(HorizontalAlign.Center)// 总时长Column(){Text(`${this.getWeeklyStats().totalDuration}`).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#10b981')Text('分钟').fontSize(12).fontColor('#64748b')}.width('33%').alignItems(HorizontalAlign.Center)// 总消耗Column(){Text(`${this.getWeeklyStats().totalCalories}`).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#10b981')Text('千卡').fontSize(12).fontColor('#64748b')}.width('33%').alignItems(HorizontalAlign.Center)}.width('100%')}.width('100%').padding(20).backgroundColor('#ffffff').borderRadius(16).margin({left:16,right:16,bottom:16})

7. 删除记录

删除不需要的运动记录。

privatedeleteRecord(id:number){this.records=this.records.filter(record=>record.id!==id)}

8. 类型选择

使用 Select 组件选择运动类型。

Select(this.exerciseTypes.map(type=>({value:type}))).value(this.newType).width('100%').height(44).onSelect((index:number)=>{this.newType=this.exerciseTypes[index]})

9. 输入验证

验证用户输入的数据是否有效。

privatevalidateDuration(input:string):boolean{constduration=parseInt(input)return!isNaN(duration)&&duration>0&&duration<=480// 最大8小时}privatevalidateCalories(input:string):boolean{constcalories=parseInt(input)return!isNaN(calories)&&calories>0&&calories<=5000// 最大5000千卡}

10. 数据格式化

格式化显示数据,使其更加易读。

// 格式化时长显示privateformatDuration(minutes:number):string{if(minutes<60){return`${minutes}分钟`}consthours=Math.floor(minutes/60)constmins=minutes%60returnmins>0?`${hours}小时${mins}分钟`:`${hours}小时`}// 格式化卡路里显示privateformatCalories(calories:number):string{if(calories>=1000){return`${(calories/1000).toFixed(1)}千卡`}return`${calories}千卡`}

完整代码解析

页面结构

┌─────────────────────────────────┐ │ [运动记录] [+] │ ├─────────────────────────────────┤ │ ┌───────────────────────────┐ │ │ │ 本周统计 │ │ │ │ 3次运动 135分钟 1000千卡│ │ │ └───────────────────────────┘ │ ├─────────────────────────────────┤ │ 运动记录 │ │ ┌───────────────────────────┐ │ │ │ 🏃 跑步 │ │ │ │ 45分钟 · 350千卡 │ │ │ │ 晨跑5公里 │ │ │ │ 今天 [×] │ │ │ └───────────────────────────┘ │ │ ┌───────────────────────────┐ │ │ │ 💪 健身 │ │ │ │ 60分钟 · 400千卡 │ │ │ │ 上肢训练 │ │ │ │ 昨天 [×] │ │ │ └───────────────────────────┘ │ │ ┌───────────────────────────┐ │ │ │ 🏊 游泳 │ │ │ │ 30分钟 · 250千卡 │ │ │ │ 自由泳1000米 │ │ │ │ 01月18日 [×] │ │ │ └───────────────────────────┘ │ └─────────────────────────────────┘

核心方法

1. 添加记录
privateaddRecord(){constduration=parseInt(this.newDuration)constcalories=parseInt(this.newCalories)if(isNaN(duration)||duration<=0){return}if(isNaN(calories)||calories<=0){return}constnewRecord:ExerciseRecord={id:Date.now(),type:this.newType,duration:duration,calories:calories,date:this.getTodayString(),notes:this.newNotes.trim()}this.records.unshift(newRecord)this.clearForm()this.showAddRecord=false}
2. 获取本周统计
privategetWeeklyStats(){consttoday=newDate()constweekAgo=newDate(today.getTime()-7*24*60*60*1000)constweeklyRecords=this.records.filter(record=>{constrecordDate=newDate(record.date)returnrecordDate>=weekAgo&&recordDate<=today})return{count:weeklyRecords.length,totalDuration:weeklyRecords.reduce((sum,record)=>sum+record.duration,0),totalCalories:weeklyRecords.reduce((sum,record)=>sum+record.calories,0)}}
3. 删除记录
privatedeleteRecord(id:number){this.records=this.records.filter(record=>record.id!==id)}

常见问题与解决方案

问题1:输入验证不严格

现象:可以输入负数或非数字字符。

解决方案

constduration=parseInt(this.newDuration)if(isNaN(duration)||duration<=0){// 显示错误提示return}

问题2:日期过滤不准确

现象:本周统计包含了非本周的记录。

解决方案

// 确保正确计算一周的时间范围constweekAgo=newDate(today.getTime()-7*24*60*60*1000)// 使用日期对象进行比较constweeklyRecords=this.records.filter(record=>{constrecordDate=newDate(record.date)returnrecordDate>=weekAgo&&recordDate<=today})

问题3:统计数据不更新

现象:添加或删除记录后,统计数据没有变化。

解决方案

// 确保 getWeeklyStats() 方法在每次渲染时重新计算// 不要缓存统计数据,而是每次调用时重新计算privategetWeeklyStats(){// 每次调用都重新计算// ...}

扩展学习

可添加功能

  1. GPS定位

    • 记录运动轨迹
    • 计算运动距离
    • 显示运动路线地图
  2. 心率监测

    • 连接心率设备
    • 记录心率数据
    • 心率区间分析
  3. 运动计划

    • 制定运动计划
    • 计划完成追踪
    • 智能推荐运动
  4. 社交分享

    • 分享运动成果
    • 好友排行榜
    • 运动挑战
  5. 数据分析

    • 运动趋势图表
    • 消耗卡路里分析
    • 运动效果评估

总结

通过本教程,您学会了:

  1. 数据统计:如何使用 reduce 方法进行数据累加和统计。

  2. 图标映射:如何使用对象映射实现图标选择。

  3. 日期处理:如何获取、格式化和比较日期。

  4. 表单处理:如何处理用户输入和表单验证。

  5. 列表渲染:如何使用 List 和 ForEach 渲染数据列表。

  6. 数据格式化:如何格式化显示数据,使其更加易读。

这些知识点可以应用于各种数据记录和统计分析类应用的开发。

http://www.gsyq.cn/news/1439762.html

相关文章:

  • 中文BERT抽取式问答实战包:PyTorch版知乎数据训练全流程(含预处理、模型、脚本与预训练权重)
  • 山东皇固金属 - 博客万
  • 别再傻傻轮询了!用STM32F1的DMA双缓存接收不定长数据,CPU占用率直降90%
  • 微信小程序单击元素切换元素的显示和隐藏
  • 哈尔滨黄金回收市场现状与六家正规机构实操指南 - 专业黄金回收
  • 越过山丘:35+ Java程序员的破局与重生——从“青春饭”到“长青树”的职业跃迁指南
  • SI9000损耗仿真实操:从FR4到高速板材,你的5英寸走线在10GHz下“掉血”多少?
  • 北京老旧小区黄金变现难?足不出户上门回收成新趋势 - 黄金上门回收
  • 如何用10MB的G-Helper替代臃肿的华硕奥创中心:终极轻量控制指南
  • 智慧树刷课插件:5分钟实现课程自动化学习的高效解决方案
  • 遗传算法调参实战:如何让你的流水车间调度(FSP)求解又快又准?
  • AI时代下Java新兵的“诺曼底登陆”——2026届Java毕业生的全新职业规划
  • 组合计数 + 拓扑序计数问题
  • 护发精油功效对比测评:抚平毛躁哪家强? - 资讯快报
  • 260亿美元估值!Cognition AI融资背后,AI编程赛道机遇与挑战并存
  • 百度网盘下载加速终极指南:3种方法突破限速实现高速下载
  • 2026 乐清黄金回收|铂金钻石 K 金名表名包回收靠谱商家推荐 - 同城好物推荐官
  • 告别Docker Hub抽风:手把手教你用SSH给群晖NAS安装ddns-go动态域名
  • Dictionary的底层原理
  • 极限运动场施工为什么不能只看效果图? - 长华体育
  • 2026年5月邯郸靠谱黄金回收门店实测盘点:余生黄金回收984元/克领跑,全城6家口碑排行 - 余生黄金回收
  • 基于机器学习的智能电表用电异常检测与负荷预测系统实战
  • 吕梁 cppm 培训机构中供国培首选 - 中供国培
  • 2026.5.30 zsh题单
  • 智慧树学习助手:用自动化技术提升在线学习效率
  • 闲管家邀请码折扣码是什么 闲管家智能回复 - 李先生sir
  • Voclosporin伏环孢素作为钙调神经磷酸酶抑制剂治疗活动性狼疮肾炎的蛋白尿降低
  • 余生黄金回收综合实力登顶!2026年5月兰州黄金回收深度解析与服务阶梯指南 - 余生黄金回收
  • 从BibTeX到完美排版:我的Mendeley/Zotero自定义CSL格式踩坑全记录
  • EP0 Oh my zsh 快速安装