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

鸿蒙原生应用实战(二):训练详情页与计时器功能

⏱️ 鸿蒙原生应用实战(二):训练详情页与计时器功能

系列目录

  • 第1篇:项目初始化与首页开发
  • 第2篇:训练详情页与计时器功能 ←当前
  • 第3篇:历史记录与日历热力图
  • 第4篇:身体数据记录与趋势分析
  • 第5篇:设置页面与项目总结

一、前言

上一篇我们完成了首页仪表盘的开发,实现了今日训练概览、进度追踪和快捷入口。这一篇我们进入 App 的核心——训练详情页

训练详情页是用户实际训练时交互最多的页面,需要包含:

  • 当前训练动作展示(名称、目标肌群、组数次数)
  • 计时器(开始/暂停/重置)
  • 完成/跳过操作
  • 全部动作的进度总览
  • 训练完成的庆祝弹窗

二、页面导航流程

用户在首页点击「开始训练 >」按钮后,通过路由跳转到训练详情页:

// Index.etsButton('开始训练 >').onClick(()=>{router.pushUrl({url:'pages/WorkoutPage'});})

三、页面整体布局

训练详情页分为三个区域:

┌────────────────────────────┐ │ ← 返回 训练中 1/4 │ ← 顶部导航栏 ├────────────────────────────┤ │ ████████████░░░░░░░░░░░░ │ ← 进度条 ├────────────────────────────┤ │ │ │ 💪 │ │ 俯卧撑 │ │ 胸部 · 三头肌 │ │ 4组 × 15次 │ │ │ │ ┌──────────┐ │ │ │ 00:42 │ │ ← 计时器 │ │ ▶ 开始 │ │ │ └──────────┘ │ │ │ │ 已完成组数: 2/4 │ │ │ │ 📋 全部动作 │ │ 💪 俯卧撑 ◀ 进行中 │ │ 🦵 深蹲 │ │ 🏋️ 引体向上 │ │ 🧘 平板支撑 │ ├────────────────────────────┤ │ [跳过 ⏭] [✅ 完成这组] │ ← 底部操作栏 └────────────────────────────┘

四、核心功能实现

4.1 页面状态管理

页面需要管理多个状态变量:

@StatecurrentWorkoutIndex:number=0;// 当前训练到第几个动作@StatetimerSeconds:number=0;// 计时秒数@StateisTimerRunning:boolean=false;// 计时器是否运行@StatetimerDisplay:string='00:00';// 计时显示文本@StateworkoutItems:WorkoutItem[]=[];// 所有训练动作@StatecompletedWorkouts:string[]=[];// 已完成的动作ID@StateshowFinishDialog:boolean=false;// 是否显示完成弹窗

4.2 训练动作展示

当前动作需要展示图标、名称、目标肌群和组数次数。由于 ArkTS 的build()方法内不能声明局部变量,我们使用@Builder抽离:

@BuildercurrentWorkoutCard(){Column(){Text(this.workoutItems[this.currentWorkoutIndex].icon).fontSize(72).margin({top:24})Text(this.workoutItems[this.currentWorkoutIndex].name).fontSize(32).fontWeight(FontWeight.Bold).margin({top:8})Text(this.workoutItems[this.currentWorkoutIndex].target).fontSize(16).fontColor('#999').margin({top:4})Text(`${this.workoutItems[this.currentWorkoutIndex].sets}组 ×${this.workoutItems[this.currentWorkoutIndex].reps}`).fontSize(18).fontColor('#FF6B35').fontWeight(FontWeight.Medium).margin({top:12})}.width('100%').alignItems(HorizontalAlign.Center)}

⚠️ 这里要注意:this.workoutItems[this.currentWorkoutIndex]@Builder中直接引用,不需要额外传参,因为@Builder会自动绑定当前 struct 的this上下文。


4.3 计时器实现

计时器是训练页面中最核心的功能。我们使用setInterval实现每秒计时。

4.3.1 开始计时
startTimer():void{if(this.isTimerRunning)return;// 防止重复启动this.isTimerRunning=true;this.timerId=setInterval(()=>{this.timerSeconds++;this.updateTimerDisplay();},1000);}
4.3.2 暂停计时
stopTimer():void{this.isTimerRunning=false;if(this.timerId!==-1){clearInterval(this.timerId);this.timerId=-1;}}
4.3.3 重置计时
resetTimer():void{this.stopTimer();this.timerSeconds=0;this.timerDisplay='00:00';}
4.3.4 显示格式化
updateTimerDisplay():void{constmin=Math.floor(this.timerSeconds/60);constsec=this.timerSeconds%60;this.timerDisplay=`${min<10?'0':''}${min}:${sec<10?'0':''}${sec}`;}
4.3.5 页面生命周期管理

关键!离开页面时必须清除计时器,否则会造成内存泄漏:

aboutToDisappear():void{this.stopTimer();}

aboutToDisappear是组件的生命周期钩子,在页面销毁前自动调用。

计时器 UI 设计

Column(){Text(this.timerDisplay).fontSize(56).fontWeight(FontWeight.Bold).fontColor('#333').fontFamily('Courier New')// 等宽字体,计时器专用Row(){if(!this.isTimerRunning){Button('▶ 开始').backgroundColor('#4CAF50').onClick(()=>this.startTimer())}else{Button('⏸ 暂停').backgroundColor('#FF9800').onClick(()=>this.stopTimer())}Button('↺ 重置').backgroundColor('#9E9E9E').onClick(()=>this.resetTimer())}}

按钮文案随状态变化(开始 ↔ 暂停),使用条件渲染实现。


4.4 完成与跳过逻辑

4.4.1 完成当前动作
completeCurrent():void{constcurrent=this.workoutItems[this.currentWorkoutIndex];if(!this.completedWorkouts.includes(current.id)){this.completedWorkouts.push(current.id);}if(this.currentWorkoutIndex<this.workoutItems.length-1){this.currentWorkoutIndex++;// 进入下一个动作this.resetTimer();// 重置计时器}else{this.stopTimer();this.showFinishDialog=true;// 全部完成,弹窗庆祝}}
4.4.2 跳过动作
skipToNext():void{if(this.currentWorkoutIndex<this.workoutItems.length-1){this.currentWorkoutIndex++;this.resetTimer();}}

4.5 全部动作列表进度

页面底部展示所有动作的缩略列表,清晰标识进度:

ForEach(this.workoutItems,(item:WorkoutItem,index:number)=>{Row(){Text(item.icon).fontSize(20)Text(item.name).fontSize(14).fontColor(index===this.currentWorkoutIndex?'#FF6B35':'#333')if(this.completedWorkouts.includes(item.id)){Text('✅')}elseif(index<this.currentWorkoutIndex){Text('⏭️')}elseif(index===this.currentWorkoutIndex){Text('◀ 进行中').fontSize(12).fontColor('#FF6B35')}}.backgroundColor(index===this.currentWorkoutIndex?'rgba(255,107,53,0.08)':'#FFFFFF')},(item:WorkoutItem)=>item.id)

四种状态标识清晰:

  • ✅ 已完成
  • ⏭️ 已跳过
  • ◀ 进行中(高亮背景)
  • 未开始(无标识)

4.6 完成庆祝弹窗

全部动作完成后,使用bindContentCover弹出半屏模态弹窗:

.bindContentCover($$this.showFinishDialog,this.finishDialog())

$$是 ArkTS 中的双向绑定语法,弹窗的显示/隐藏由this.showFinishDialog控制。

弹窗内容:

@BuilderfinishDialog(){Column(){Text('🎉').fontSize(64)Text('训练完成!').fontSize(24).fontWeight(FontWeight.Bold)Text('太棒了,继续保持!').fontSize(16).fontColor('#999')Text(`总用时:${this.timerDisplay}`).fontSize(14).fontColor('#FF6B35')Button('返回首页').width('80%').backgroundColor('#FF6B35').borderRadius(22).onClick(()=>{this.showFinishDialog=false;router.back();})}.padding(32).backgroundColor('#FFFFFF').borderRadius(24).alignItems(HorizontalAlign.Center)}

五、关键技术与踩坑

5.1@Builder的正确用法

@Builder是 ArkTS 中封装可复用 UI 片段的利器,但有几点需要注意:

  1. 必须是 struct 内部方法,用@Builder装饰
  2. 可以在build()中直接调用(用this.xxx()语法)
  3. 可以带参数,也可以不带参数直接引用this的属性

5.2setInterval与页面生命周期

计时器一定要在aboutToDisappear中清除,否则:

  • 页面跳转后计时器还在跑
  • 再次进入页面会启动多个计时器
  • 造成内存泄漏

5.3 双向绑定$$

$$语法实现数据的双向绑定,常用于:

  • bindContentCover($$this.showFinishDialog, ...)— 弹窗显示绑定
  • TextInput({ text: $$this.inputValue })— 输入框双向绑定

5.4 条件渲染

ArkTS 不支持v-if/v-show,但可以直接在build()中使用if/else

if(this.isTimerRunning){Button('⏸ 暂停')}else{Button('▶ 开始')}


六、下篇预告

下一篇我们将开发历史记录页面,包含:

  • 月历组件(月份切换、训练日标记)
  • 统计卡片(本月训练次数、总时长、连续天数)
  • 周训练时长柱状图

下一篇:历史记录与日历热力图 →

http://www.jsqmd.com/news/993202/

相关文章:

  • TESSERA:打破遥感模型依赖「理想数据」瓶颈,低标注下优势显著
  • MPC8309硬件设计实战:时钟、电气与PCB布局关键解析
  • 毕业设计 yolov11骨折检测医疗辅助系统(源码+论文)
  • 人事业务融合型系统协同能力评测:泛微・聚才林基准评估
  • GASDocumentation:虚幻引擎5能力系统实战解析与架构设计
  • 如何用AI智能体在30分钟内搭建专业股票分析系统:从小白到量化交易高手
  • 5分钟搞定黑苹果EFI:OpCore-Simplify自动化配置工具完全指南
  • 2026免费去水印工具推荐!在线/电脑/手机通用教程
  • MetaERP Oracle EBS 顾问转型 MetaERP 30 天学习路线图
  • B站视频下载技术实现:基于Python的高清视频下载工具深度解析
  • 6秒完成六源分离:htdemucs_6s如何重新定义音频处理效率
  • 2026年安徽初三考不上高中男孩适合上哪些专业? - 我叫小周
  • UltraRAG:如何用50行YAML代码构建智能调查报告生成系统?
  • 如何将单张插画智能转换为专业PSD分层文件:Layerdivider完全指南
  • 2026通关榜!好用的降AIGC网站全测评,效率直接拉满!
  • 4步解决老旧Mac升级难题:OpenCore Legacy Patcher完整实战指南
  • Abaqus批量弹簧脚本避坑指南:手把手教你处理SyntaxError和节点匹配问题
  • 面向产出物的思维能力和 AI 交互
  • MetaERP SAP顾问转MetaERP 30天技能提升计划+核心交付模板清单
  • MPV懒人包:5分钟打造专业级Windows视频播放器
  • 语言的未来:是继续辉煌还是逐渐衰落?
  • Spring Security多用户登录实战:手把手教你改造若依框架,让会员和后台管理员分开登录
  • 2026 年 6 月最新 | 大流量砂磨机厂家推荐 专业大流量砂磨机生产企业 - 商业新知
  • 2026选有保障的玻璃钢管道生产厂家 3个核心判断标准 - 资讯速览
  • 大麦自动化抢票:从手动秒杀到技术降维打击的技术实现解析
  • 终极Project Sekai表情包制作指南:3分钟创建个性化Discord贴纸
  • 5分钟快速上手:零安装的浏览器3D雕刻工具SculptGL完全指南
  • 163MusicLyrics:免费歌词下载神器,轻松获取网易云QQ音乐歌词
  • jQuery补充知识点
  • 2026太原贵金属回收黄金回收白银回收铂金回收店铺怎么挑?5 家不压价线下实体店完整测评清单 + 商家联络方式 - 信誉隆金银铂奢回收