【鸿蒙原生应用开发--ArkUI--013】Exercise-tracker 运动记录应用开发教程
Exercise-tracker 运动记录应用开发教程
项目介绍
项目背景
运动记录应用是一个帮助用户记录和追踪运动数据的健康管理工具。随着人们对健康生活方式的重视,运动已经成为日常生活的重要组成部分。然而,很多人在运动时缺乏系统的记录和分析,导致无法准确了解自己的运动状况和进步情况。
运动记录应用通过数字化的方式,帮助用户记录每次运动的类型、时长、消耗的卡路里等数据,并提供统计分析功能,让用户能够清晰地看到自己的运动轨迹和成果。这种可视化的反馈机制能够有效激励用户坚持运动,养成健康的生活习惯。
应用场景
健身记录:记录每次运动的详细数据,包括运动类型、持续时间、消耗卡路里等。用户可以随时查看自己的运动历史。
数据分析:分析运动习惯和趋势,了解哪些运动类型最常进行,哪些时间段运动最频繁。
目标追踪:设定运动目标并追踪完成情况,如每周运动次数、每月运动时长等。
健康监测:长期记录运动数据,为健康管理提供参考依据。
功能特性
运动记录:记录运动类型、时长、消耗卡路里和备注信息。
类型选择:支持多种运动类型,如跑步、游泳、骑行、健身、瑜伽等。
数据统计:统计本周运动次数、总时长和总消耗卡路里。
历史记录:查看历史运动记录,支持删除操作。
图标显示:根据运动类型显示相应的图标,直观易识别。
最终效果
应用采用绿色主题,象征着健康和活力。主界面包含:
- 顶部标题栏和添加按钮
- 本周统计卡片,显示运动次数、总时长和总消耗
- 运动记录列表,显示每条记录的详情
技术栈
- 开发框架: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(){// 每次调用都重新计算// ...}扩展学习
可添加功能
GPS定位
- 记录运动轨迹
- 计算运动距离
- 显示运动路线地图
心率监测
- 连接心率设备
- 记录心率数据
- 心率区间分析
运动计划
- 制定运动计划
- 计划完成追踪
- 智能推荐运动
社交分享
- 分享运动成果
- 好友排行榜
- 运动挑战
数据分析
- 运动趋势图表
- 消耗卡路里分析
- 运动效果评估
总结
通过本教程,您学会了:
数据统计:如何使用 reduce 方法进行数据累加和统计。
图标映射:如何使用对象映射实现图标选择。
日期处理:如何获取、格式化和比较日期。
表单处理:如何处理用户输入和表单验证。
列表渲染:如何使用 List 和 ForEach 渲染数据列表。
数据格式化:如何格式化显示数据,使其更加易读。
这些知识点可以应用于各种数据记录和统计分析类应用的开发。
