当前位置: 首页 > 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.rkmt.cn/news/1504958.html

相关文章:

  • TESSERA:打破遥感模型依赖「理想数据」瓶颈,低标注下优势显著
  • 毕业设计 yolov11骨折检测医疗辅助系统(源码+论文)
  • 人事业务融合型系统协同能力评测:泛微・聚才林基准评估
  • GASDocumentation:虚幻引擎5能力系统实战解析与架构设计
  • 2026免费去水印工具推荐!在线/电脑/手机通用教程
  • 如何将单张插画智能转换为专业PSD分层文件:Layerdivider完全指南
  • 面向产出物的思维能力和 AI 交互
  • 大麦自动化抢票:从手动秒杀到技术降维打击的技术实现解析
  • 163MusicLyrics:免费歌词下载神器,轻松获取网易云QQ音乐歌词
  • 如何轻松让老旧Mac焕发新生:OpenCore Legacy Patcher完整指南
  • 智慧交通港澳地区车牌检测数据集VOC+YOLO格式4167张4类别
  • 2026国内留学教育实测封神!5款上海等地国际本科机构全国口碑出众受好评 - 十大品牌榜
  • foobox终极美化指南:三分钟打造你的专属音乐播放器
  • 2026商洛贵金属回收黄金回收白银回收铂金回收店铺怎么挑?5 家不压价线下实体店完整测评清单 + 商家联络方式 - 信誉隆金银铂奢回收
  • BibiGPT完整指南:从音视频理解到高效学习的5个核心突破
  • 鸿蒙原生应用实战(三):UI构建 — 首页与写日记页面开发全流程
  • 火绒安全软件
  • 【收藏级·2026版】AI Agent记忆技术演进全解析
  • Three.js 实战:用 Vue3 打造一个可交互的3D人体解剖查看器(含完整源码)
  • 在AI的帮助下理解spring的启动过程
  • 小米穿戴设备表盘设计:从零到一的视觉创作指南
  • htdemucs_6s音乐源分离:6秒完成六音轨精准分离的革命性工具
  • 沈阳高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录 - 诚金汇钻回收公司
  • COMSOL仿真揭秘:母线板温升下的电阻动态响应
  • 企业微信模板卡片消息实战:一个PHP代码示例搞定合同审批提醒(含版本兼容说明)
  • 从[特殊字符]到[特殊字符]:手把手教你用Python爬虫批量下载并分类所有Emoji图片(附代码)
  • OpenCore Simplify:重构黑苹果配置的技术哲学与工程实践
  • Windows下用FFmpeg sws_scale做RGB图像缩放+多图定位叠加的完整工程包
  • 2026深圳GEO优化公司推荐:昊客网络助力企业AI搜索时代抢占先机 - 猫头鹰AI推广
  • 用Python+Matplotlib可视化旋转曲面:从抛物线到双曲面的3D建模实战