HarmonyOS5 運動健康app(二):健康跑步(附代碼)

一、數據模型:構建運動記錄的數字骨架

代碼通過RunRecord接口定義了跑步數據的核心結構:

interface RunRecord {id: string;         // 記錄唯一標識date: Date;         // 跑步日期distance: number;   // 距離(公里)duration: number;   // 時長(分鐘)pace: number;       // 配速(分鐘/公里)
}

這一模型以"距離-時長-配速"為核心維度,通過iddate建立時間軸索引。在實際跑步場景中,當用戶點擊"結束跑步"時,系統會基于實時數據生成RunRecord對象并添加到runRecords數組中:

const newRecord: RunRecord = {id: Date.now().toString(),date: new Date(),distance: this.currentDistance,duration: this.currentDuration,pace: this.currentPace
};
this.runRecords = [newRecord, ...this.runRecords];

這種設計遵循了"最小必要數據"原則,既滿足基礎運動分析需求,又降低了數據存儲與計算的復雜度。

二、狀態管理:實現運動數據的實時響應

應用通過@State裝飾器管理五大核心狀態,構建起數據流轉的中樞系統:

  • isRunning:標記跑步狀態(進行中/已結束),控制界面按鈕與數據展示邏輯
  • currentDistance/duration/pace:實時跑步數據,通過定時器模擬GPS更新
  • showHistory:控制歷史記錄列表的展開/收起狀態

核心狀態更新邏輯體現在startRunendRun方法中。當用戶點擊"開始跑步"時,系統啟動定時器以100ms為周期更新數據:

this.intervalId = setInterval(() => {this.currentDistance += 0.01;    // 模擬每100ms增加10米this.currentDuration += 0.1;     // 模擬每100ms增加0.1秒if (this.currentDistance > 0) {this.currentPace = this.currentDuration / this.currentDistance / 60;}
}, 100);

endRun方法則負責清除定時器、保存記錄并重置狀態,形成"數據采集-存儲-重置"的閉環。

三、UI組件:打造直觀的運動數據可視化體驗

應用采用"統計頭部-數據卡片-歷史列表"的三層布局,通過ArkTS的聲明式UI特性實現動態渲染:

  1. 統計頭部組件(StatsHeader)
    采用三欄式布局展示總跑步次數、總距離與平均配速,通過reduce方法對歷史記錄進行聚合計算:
Text(`${this.runRecords.reduce((sum, r) => sum + r.distance, 0).toFixed(1)}km`)

數據展示遵循"數值+單位"的層級結構,大號字體突出核心數字,小號字體標注單位與說明,符合用戶"先看結果再辨含義"的認知習慣。

  1. 跑步數據卡片(RunDataCard)
    通過isRunning狀態動態切換顯示邏輯:跑步中時突出距離與時長數據,搭配配速與時長的分欄展示;跑步結束后則展示今日匯總數據。按鈕設計采用綠色(開始)與紅色(結束)的高對比度配色,強化操作反饋。
  2. 歷史記錄列表(HistoryList)
    通過showHistory狀態控制顯示/隱藏,使用ForEach循環渲染歷史記錄,每條記錄包含日期、時長、距離與配速信息。當記錄為空時顯示空狀態提示,提升用戶體驗的完整性。

四、核心算法:從原始數據到運動洞察

代碼中包含三個關鍵數據處理函數,將原始運動數據轉化為可解讀的運動指標:

  • formatTime:將秒數轉換為"分:秒"格式(如123秒轉為2:03),便于用戶快速理解運動時長
  • getTodayDistance/duration/pace:通過日期篩選與數組聚合,計算今日運動數據,支持用戶查看短期運動趨勢
  • formatDate:將Date對象轉換為"月/日"格式(如6/15),簡化歷史記錄的時間展示

getTodayPace為例,其核心邏輯是通過篩選今日記錄并計算平均配速:

const totalDistance = this.getTodayDistance();
const totalDuration = this.getTodayDuration();
if (totalDistance === 0) return 0;
return totalDuration / totalDistance / 60;

五、附:代碼
import promptAction from '@ohos.promptAction';// 跑步記錄接口
interface RunRecord {id: string;date: Date;distance: number; // 公里duration: number; // 分鐘pace: number;     // 配速(分鐘/公里)
}
@Entry
@Component
struct Index {@State runRecords: RunRecord[] = [];  // 跑步記錄列表@State isRunning: boolean = false;    // 是否正在跑步@State currentDistance: number = 0;   // 當前跑步距離@State currentDuration: number = 0;   // 當前跑步時長@State currentPace: number = 0;       // 當前配速@State showHistory: boolean = false;  // 是否顯示歷史記錄private intervalId: number = -1;      // 定時器ID// 開始跑步private startRun() {this.isRunning = true;this.currentDistance = 0;this.currentDuration = 0;this.currentPace = 0;// 模擬GPS定位更新this.intervalId = setInterval(() => {this.currentDistance += 0.01;  // 模擬每100ms增加10米this.currentDuration += 0.1;   // 模擬每100ms增加0.1秒// 更新配速if (this.currentDistance > 0) {this.currentPace = this.currentDuration / this.currentDistance / 60;}}, 100);}// 結束跑步private endRun() {clearInterval(this.intervalId);// 創建新記錄const newRecord: RunRecord = {id: Date.now().toString(),date: new Date(),distance: this.currentDistance,duration: this.currentDuration,pace: this.currentPace};// 添加到記錄列表this.runRecords = [newRecord, ...this.runRecords];// 重置狀態this.isRunning = false;this.currentDistance = 0;this.currentDuration = 0;this.currentPace = 0;promptAction.showToast({ message: '跑步記錄已保存' });}// 格式化時間為分:秒private formatTime(seconds: number): string {const minutes = Math.floor(seconds / 60);const secs = Math.floor(seconds % 60);return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;}// 獲取今日跑步距離private getTodayDistance(): number {const today = new Date();today.setHours(0, 0, 0, 0);const todayRuns = this.runRecords.filter(record => {const recordDate = new Date(record.date);recordDate.setHours(0, 0, 0, 0);return recordDate.getTime() === today.getTime();});return todayRuns.reduce((sum, record) => sum + record.distance, 0);}// 獲取今日跑步時長private getTodayDuration(): number {const today = new Date();today.setHours(0, 0, 0, 0);const todayRuns = this.runRecords.filter(record => {const recordDate = new Date(record.date);recordDate.setHours(0, 0, 0, 0);return recordDate.getTime() === today.getTime();});return todayRuns.reduce((sum, record) => sum + record.duration, 0);}// 獲取今日平均配速private getTodayPace(): number {const totalDistance = this.getTodayDistance();const totalDuration = this.getTodayDuration();if (totalDistance === 0) return 0;return totalDuration / totalDistance / 60;}// 格式化日期private formatDate(date: Date): string {return `${date.getMonth() + 1}/${date.getDate()}`;}// 頭部統計組件@BuilderStatsHeader() {Column() {Text('跑步統計').fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 15 })Row() {Column() {Text(`${this.runRecords.length}`).fontSize(24).fontWeight(FontWeight.Bold)Text('總次數').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')Column() {Text(`${this.runRecords.reduce((sum, r) => sum + r.distance, 0).toFixed(1)}km`).fontSize(24).fontWeight(FontWeight.Bold)Text('總距離').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')Column() {Text(`${(this.runRecords.reduce((sum, r) => sum + r.pace, 0) / this.runRecords.length || 0).toFixed(2)}min/km`).fontSize(24).fontWeight(FontWeight.Bold)Text('平均配速').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')}.width('100%')}.width('100%').padding(15).backgroundColor('#F8F9FC').borderRadius(12)}// 跑步數據卡片@BuilderRunDataCard() {Column() {Text(this.isRunning ? '跑步中' : '今日跑步數據').fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 25 })if (this.isRunning) {// 跑步中數據顯示Column() {Text(`${this.currentDistance.toFixed(2)}km`).fontSize(42).fontWeight(FontWeight.Bold).margin({ bottom: 15 })Text(`${this.formatTime(this.currentDuration)}`).fontSize(24).margin({ bottom: 25 })Row() {Column() {Text(`${this.currentPace.toFixed(2)}min/km`).fontSize(16).fontWeight(FontWeight.Bold)Text('配速').fontSize(12).fontColor('#888').margin({top: 5})}.width('50%')Column() {Text(`${this.formatTime(this.currentDuration)}`).fontSize(16).fontWeight(FontWeight.Bold)Text('時長').fontSize(12).fontColor('#888').margin({top: 5})}.width('50%')}.width('100%')}.width('100%').alignItems(HorizontalAlign.Center).margin({ bottom: 25 })} else {// 跑步后數據顯示Row() {Column() {Text(`${this.getTodayDistance().toFixed(2)}km`).fontSize(24).fontWeight(FontWeight.Bold)Text('距離').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')Column() {Text(`${this.formatTime(this.getTodayDuration())}`).fontSize(24).fontWeight(FontWeight.Bold)Text('時長').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')Column() {Text(`${this.getTodayPace().toFixed(2)}min/km`).fontSize(24).fontWeight(FontWeight.Bold)Text('配速').fontSize(12).fontColor('#888').margin({top: 5})}.width('33%')}.width('100%').margin({ bottom: 25 })}if (this.isRunning) {Button('結束跑步').width('100%').height(45).backgroundColor('#E53935').fontColor(Color.White).fontSize(16).borderRadius(8).onClick(() => this.endRun())} else {Button('開始跑步').width('100%').height(45).backgroundColor('#2E7D32').fontColor(Color.White).fontSize(16).borderRadius(8).onClick(() => this.startRun())}}.width('100%').padding(15).backgroundColor(Color.White).borderRadius(12).shadow({ radius: 3, color: '#0000001A' })}// 歷史記錄列表@BuilderHistoryList() {if (this.showHistory) {Column() {Text('跑步歷史').fontSize(16).fontWeight(FontWeight.Bold).margin({ bottom: 15 })if (this.runRecords.length === 0) {Text('暫無跑步記錄').fontSize(14).fontColor('#AAA').margin({ top: 40 })} else {List() {ForEach(this.runRecords, (record: RunRecord) => {ListItem() {Row() {Column() {Text(this.formatDate(record.date)).fontSize(14)Text(`${this.formatTime(record.duration)}`).fontSize(12).fontColor('#888').margin({top: 4})}.width('40%')Column() {Text(`${record.distance}km`).fontSize(14).fontWeight(FontWeight.Bold)Text(`${record.pace.toFixed(2)}min/km`).fontSize(12).fontColor('#888').margin({top: 4})}.width('60%')}.width('100%').padding(8)}})}}}.width('100%').padding(15).backgroundColor('#F8F9FC').borderRadius(12).layoutWeight(1)}}build() {Column() {// 統計頭部this.StatsHeader()// 跑步數據卡片this.RunDataCard()// 歷史記錄this.HistoryList()// 底部按鈕Button(this.showHistory ? '隱藏歷史' : '顯示歷史').width('100%').margin({ top: 15 }).height(40).fontSize(14).borderRadius(8).backgroundColor('#E0E0E0').fontColor('#333').onClick(() => {this.showHistory = !this.showHistory;})}.width('100%').height('100%').padding(12).backgroundColor('#FCFDFF')}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909316.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909316.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909316.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

29-Oracle 23ai Flashback Log Placement(閃回日志靈活配置)

小伙伴們有沒有被各種存儲路徑滿導致的業務崩&#xff0c;半夜起來清理的經歷。一不小心 FRA寫滿了&#xff0c;導致了實例hang住。 OCM考試&#xff0c;時不時就會冒出來這個直接給instance hang&#xff0c;本就卡的環境中腦袋都卡殼、無從下手&#xff0c;一臉懵直接崩。 …

React表單處理:如何獲取輸入框(input)的值?(受控組件)

系列回顧&#xff1a; 在前面的文章中&#xff0c;我們已經掌握了State、Props、事件處理、列表渲染和條件渲染。我們的應用已經能展示動態內容并響應用戶的點擊。現在&#xff0c;我們要 tackling 一個非常常見的需求&#xff1a;如何獲取用戶在表單輸入框&#xff08;<inp…

探索現代 Web 開發:從 HTML5 到 Vue.js 的全棧之旅

在當今快速發展的互聯網時代&#xff0c;Web 開發已經成為構建數字世界的重要基石。無論是企業級應用、社交媒體平臺&#xff0c;還是個人博客和電商平臺&#xff0c;Web 技術都在背后默默支撐著這些系統的運行。隨著前端技術的不斷演進&#xff0c;開發者們已經不再局限于傳統…

ElasticSearch聚合查詢從15秒到1.2秒的深度優化實踐

一、問題背景 在金融風控場景中,我們需要對90天內的交易數據進行多維度聚合分析(按風險等級、地區、金額分段等)。隨著數據量增長到日均3000萬+記錄,原有查詢響應時間逐漸惡化至15秒以上,嚴重影響了業務決策效率。 二、原始架構性能分析 1. 集群拓撲 # 原單節點配置 N…

2025.06.09【讀書筆記】|PromptBio:讓生信分析更簡單的AI平臺

文章目錄 一、PromptBio 是什么&#xff1f;二、主要功能介紹1. 對話式智能體&#xff0c;像聊天一樣做分析2. 自動化工作流&#xff0c;省時省力3. 數據管理一站式搞定4. 機器學習也能一鍵搞定5. “無代碼”到“全代碼”&#xff0c;人人都能用 三、適合哪些人用&#xff1f;四…

實戰解析:如何用克魔(KeyMob)等工具構建iOS應用穩定性與數據可觀測體系

在iOS開發項目逐漸走向復雜化的今天&#xff0c;團隊對“可觀測性”的要求正不斷提升。開發者不僅要知道App是否運行正常&#xff0c;更要明確“為什么異常、在哪里異常、是否可復現”。傳統的調試工具往往側重單一維度&#xff0c;要么是資源監控、要么是日志分析&#xff0c;…

如何輕松實現多源混算報表

報表作為綜合業務&#xff0c;數據來源多種多樣。傳統實現多源混合查詢報表要通過 ETL 將數據同庫&#xff0c;但這種方式數據時效性太差使用場景受限。通過邏輯數倉能獲得較強的數據實時性&#xff0c;但體系又過于沉重&#xff0c;為報表業務搭建邏輯數倉有點得不償失。需要一…

Docker|簡單入門

文章目錄 Docker簡介Docker和虛擬機的聯系和區別基本原理和概念鏡像容器倉庫 Docker安裝配置容器化和Dockerfile實踐環節Docker Compose Docker簡介 Docker是一個用于構建build、運行run、傳送share應用程序的平臺&#xff0c;可以把應用程序打包成一個個的集裝箱&#xff0c;…

阿里云云原生數據庫PolarDB和普通云數據庫的區別?

文章目錄 前言一、云數據庫的演進&#xff1a;從“托管”到“原生”的跨越二、PolarDB的核心創新&#xff1a;重新定義云數據庫的能力邊界1. 存算分離架構&#xff1a;打破資源綁定的“枷鎖”2. 多模引擎與兼容生態&#xff1a;降低應用遷移成本3. 智能化運維&#xff1a;讓數據…

SNN學習(4):真實的生物神經學中神經元和人腦結構學習

目錄 一、基礎知識 1 簡單神經元回路中的信號運作 2 高級功能相關的復雜神經元回路 3 細胞體、樹突和軸突 3.1 神經元細胞 3.2 非神經元細胞 3.3 神經膠質細胞 3.4 神經細胞的信號傳遞 3.4.1 動作電位的特性 3.4.2 興奮和抑制 3.4.3 電傳遞 二、大腦皮層及視覺系統…

第六天 界面操作及美化(6.1 建立菜單及異步調用)

6.1 建立菜單及異步調用 在程序中&#xff0c;菜單&#xff08;Menu&#xff09;是一種常見的用戶界面元素&#xff0c;在程序中起到了組織功能、提高用戶體驗、提供快捷方式和幫助文檔等重要作用。通過合理使用菜單&#xff0c;可以使程序的功能更加清晰、操作更加便捷&#…

論文解析:一文弄懂ResNet(圖像識別分類、目標檢測)

目錄 一、相關資源 二、Motivation 三、技術細節 1.殘差學習過程 2.快捷連接類型 (1)Identity Shortcuts&#xff08;恒等捷徑&#xff09; (2)Projection Shortcuts&#xff08;投影捷徑&#xff09; (3)兩種捷徑對比 3.深層瓶頸結構Deeper Bottleneck Architectures…

動態規劃算法的歡樂密碼(二):路徑問題

專欄&#xff1a;算法的魔法世界 個人主頁&#xff1a;手握風云 一、例題講解 1.1. 不同路徑 題目要求是計算從網格的左上角&#xff08;起點&#xff09;到右下角&#xff08;終點&#xff09;的所有不同路徑的數量。機器人每次只能向下或向右移動一步。如下圖所示&#xff0…

嵌入式相關開源項目、庫、資料------持續更新中

嵌入式相關開源項目、庫、資料------持續更新中 學習初期最難找的就是找學習資料了&#xff0c;本貼精心匯總了一些嵌入式相關資源&#xff0c;包括但不限于編程語言、單片機、開源項目、物聯網、操作系統、Linux、計算機等資源&#xff0c;并且在不斷地更新中&#xff0c;致力…

圖像處理與機器學習項目:特征提取、PCA與分類器評估

圖像處理與機器學習項目:特征提取、PCA與分類器評估 項目概述 本項目將完成一個完整的圖像處理與機器學習流程,包括數據探索、特征提取、主成分分析(PCA)、分類器實現和評估五個關鍵步驟。我們將使用Python的OpenCV、scikit-learn和scikit-image庫來處理圖像數據并實現機器…

MATLAB | 如何使用MATLAB獲取《Nature》全部繪圖 (附23-25年圖像)

文末有全部圖片資源 我在兩年前更過如何用 MATLAB 爬取 《Nature》全部插圖&#xff0c;最近又有人問我有沒有下載好的24&#xff0c;25年插圖的壓縮包&#xff0c;于是又去拿代碼運行了一下&#xff0c;發現兩年前寫的代碼今天居然還能用&#xff0c;代碼如下&#xff1a; f…

中國老年健康調查(CLHLS)數據挖掘教程(1)--CLHLS簡介和數據下載

北京大學“中國老年健康影響因素跟蹤調查&#xff08;簡稱‘中國老年健康調查’&#xff1b;英文名稱為Chinese Longitudinal Healthy Longevity Survey (CLHLS)&#xff09;”及交叉學科研究由國家自然科學基金委主任基金應急項目、重大項目、重點項目及國際合作項目。1998-20…

基本多線程編譯make命令

背景&#xff1a; 在ffmpeg源碼編譯的時候要等很久&#xff0c;快下班了&#xff0c;等不及。 解決方法&#xff1a; 使用多線程編譯。 make -j{n} 如&#xff1a; make -j8詳解&#xff1a;&#xff08;沒時間看的可以返回了&#xff01;&#xff09; 在編譯 FFmpeg 時使用…

MNIST數據集上樸素貝葉斯分類器(MATLAB例)

MNIST數據集上樸素貝葉斯分類器 Naive Bayes Classification fitcnb Train multiclass naive Bayes model Syntax Mdl fitcnb(Tbl,ResponseVarName) Mdl fitcnb(Tbl,formula) Mdl fitcnb(Tbl,Y) Mdl fitcnb(X,Y) Mdl fitcnb(___,Name,Value) [Mdl,AggregateOptimization…

網站設計小技巧:利用交互設計提升用戶體驗

現在很多企業朋友都會感覺到&#xff0c;做網站設計掌握不好設計網頁的魂&#xff0c;換了很多設計方式可能效果都不理想。蒙特網站專注高端網站建設20多年&#xff0c;基于為華為、字節跳動、海康威視等頭部企業打造網站的經驗&#xff0c;今天將近期用戶比較喜歡的網頁設計方…