一個功能完整的鴻蒙健身追蹤器應用,全面展示了ArkTS在健康數據管理、運動記錄、進度追蹤和可視化展示等方面的核心能力。主要功能包括:
- 運動記錄:記錄步數、跑步距離、卡路里消耗等健康數據
- 目標設定:設置每日運動目標並追蹤完成進度
- 數據統計:顯示今日、本週、本月的運動數據統計
- 健康分析:提供簡單的健康建議和趨勢分析
- 成就係統:解鎖運動成就,激勵用户堅持鍛鍊
- 歷史記錄:查看歷史運動數據變化趨勢
- 個人資料:管理用户基本信息和個人目標
代碼邏輯分析
應用採用"健康數據驅動UI"的架構設計:
- 初始化階段:應用啓動時,加載用户健康數據和運動記錄
- 狀態管理:使用多個
@State裝飾器管理運動數據、目標設置、統計信息和用户資料 - 數據記錄流程:
- 手動記錄運動 → 更新今日數據 → 重新計算統計信息
- 自動模擬數據 → 定時更新步數 → 實時刷新顯示
- 目標追蹤:
- 設置運動目標 → 計算完成進度 → 更新進度顯示
- 達成目標 → 顯示慶祝效果 → 更新成就狀態
- 統計分析:
- 聚合歷史數據 → 計算趨勢變化 → 生成可視化圖表
- 比較不同週期 → 提供健康建議 → 更新分析結果
- 界面更新:所有數據變化實時反映在UI上,提供流暢的用户體驗
完整代碼
@Entry
@Component
struct FitnessTrackerTutorial {
@State stepCount: number = 0;
@State distance: number = 0;
@State calories: number = 0;
@State dailyGoal: number = 10000;
@State workoutHistory: WorkoutRecord[] = [];
@State currentView: string = 'dashboard';
@State userProfile: UserProfile = new UserProfile();
@State achievements: Achievement[] = [];
aboutToAppear() {
this.loadSampleData();
this.startStepSimulation();
}
build() {
Column({ space: 0 }) {
this.BuildNavigation()
if (this.currentView === 'dashboard') {
this.BuildDashboard()
} else if (this.currentView === 'history') {
this.BuildHistoryView()
} else if (this.currentView === 'profile') {
this.BuildProfileView()
} else if (this.currentView === 'achievements') {
this.BuildAchievementsView()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA')
}
@Builder BuildNavigation() {
Row({ space: 0 }) {
this.BuildNavItem('數據', 'dashboard', '📊')
this.BuildNavItem('歷史', 'history', '📈')
this.BuildNavItem('成就', 'achievements', '🏆')
this.BuildNavItem('我的', 'profile', '👤')
}
.width('100%')
.height(60)
.backgroundColor('#FFFFFF')
.shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: 1 })
}
@Builder BuildNavItem(title: string, view: string, icon: string) {
Button(icon + '\n' + title)
.onClick(() => {
this.currentView = view;
})
.backgroundColor(this.currentView === view ? '#4A90E2' : '#FFFFFF')
.fontColor(this.currentView === view ? '#FFFFFF' : '#666666')
.fontSize(12)
.borderRadius(0)
.layoutWeight(1)
.height(60)
}
@Builder BuildDashboard() {
Scroll() {
Column({ space: 20 }) {
this.BuildTodayOverview()
this.BuildGoalProgress()
this.BuildQuickActions()
this.BuildHealthTips()
}
.width('100%')
.padding(20)
}
.width('100%')
.layoutWeight(1)
}
@Builder BuildTodayOverview() {
Column({ space: 15 }) {
Text('今日運動')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Row({ space: 20 }) {
this.BuildMetricCard('步數', this.stepCount.toString(), '👣')
this.BuildMetricCard('距離', this.distance.toFixed(1) + 'km', '🛣️')
this.BuildMetricCard('卡路里', this.calories.toString(), '🔥')
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildMetricCard(title: string, value: string, icon: string) {
Column({ space: 8 }) {
Text(icon)
.fontSize(24)
Text(value)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
Text(title)
.fontSize(12)
.fontColor('#666666')
}
.layoutWeight(1)
.padding(15)
.backgroundColor('#F8F9FA')
.borderRadius(12)
}
@Builder BuildGoalProgress() {
const progress = Math.min(this.stepCount / this.dailyGoal, 1);
Column({ space: 15 }) {
Row({ space: 10 }) {
Text('今日目標')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.layoutWeight(1)
Text(`${this.stepCount} / ${this.dailyGoal}`)
.fontSize(16)
.fontColor('#666666')
}
.width('100%')
Stack() {
Rect()
.width('100%')
.height(12)
.fill('#E9ECEF')
.borderRadius(6)
Rect()
.width(`${progress * 100}%`)
.height(12)
.fill(progress >= 1 ? '#28A745' : '#4A90E2')
.borderRadius(6)
}
.width('100%')
.height(12)
if (progress >= 1) {
Text('🎉 恭喜完成今日目標!')
.fontSize(14)
.fontColor('#28A745')
.fontWeight(FontWeight.Medium)
}
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildQuickActions() {
Column({ space: 15 }) {
Text('快速記錄')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Row({ space: 15 }) {
Button('步行\n+1000步')
.onClick(() => {
this.addSteps(1000);
})
.backgroundColor('#4A90E2')
.fontColor('#FFFFFF')
.borderRadius(12)
.layoutWeight(1)
.height(80)
Button('跑步\n+2km')
.onClick(() => {
this.addWorkout('running', 2000, 120);
})
.backgroundColor('#FF6B6B')
.fontColor('#FFFFFF')
.borderRadius(12)
.layoutWeight(1)
.height(80)
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildHealthTips() {
const tip = this.getHealthTip();
Column({ space: 12 }) {
Row({ space: 10 }) {
Text('💡')
.fontSize(18)
Text('健康建議')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.layoutWeight(1)
}
.width('100%')
Text(tip)
.fontSize(14)
.fontColor('#666666')
.lineHeight(20)
}
.width('100%')
.padding(20)
.backgroundColor('#E7F3FF')
.borderRadius(16)
}
@Builder BuildHistoryView() {
Column({ space: 20 }) {
Text('運動歷史')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.margin({ top: 20 })
if (this.workoutHistory.length === 0) {
this.BuildEmptyState('暫無運動記錄', '開始你的第一次運動吧!')
} else {
List() {
ForEach(this.workoutHistory.slice().reverse(), (record: WorkoutRecord) => {
ListItem() {
this.BuildHistoryItem(record)
}
})
}
.width('100%')
.layoutWeight(1)
.backgroundColor(Color.Transparent)
}
}
.width('100%')
.padding(20)
}
@Builder BuildHistoryItem(record: WorkoutRecord) {
Row({ space: 15 }) {
Column({ space: 5 }) {
Text(record.date)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Text(`${record.steps} 步 · ${record.distance}km · ${record.calories}卡`)
.fontSize(14)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
}
.layoutWeight(1)
Text(this.getWorkoutIcon(record.type))
.fontSize(20)
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ bottom: 10 })
}
@Builder BuildProfileView() {
Scroll() {
Column({ space: 20 }) {
this.BuildUserInfo()
this.BuildGoalSetting()
this.BuildStatistics()
}
.width('100%')
.padding(20)
}
.width('100%')
.layoutWeight(1)
}
@Builder BuildUserInfo() {
Column({ space: 15 }) {
Text('個人資料')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Row({ space: 15 }) {
Column({ space: 8 }) {
Text(this.userProfile.name)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
Text(`${this.userProfile.age}歲 · ${this.userProfile.height}cm · ${this.userProfile.weight}kg`)
.fontSize(14)
.fontColor('#666666')
}
.layoutWeight(1)
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildGoalSetting() {
Column({ space: 15 }) {
Text('目標設置')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Row({ space: 10 }) {
Text('每日步數目標:')
.fontSize(16)
.fontColor('#666666')
.layoutWeight(1)
TextInput({ text: this.dailyGoal.toString() })
.onChange((value: string) => {
const newGoal = parseInt(value) || 10000;
this.dailyGoal = Math.max(1000, Math.min(50000, newGoal));
})
.type(InputType.Number)
.width(100)
.textAlign(TextAlign.Center)
.backgroundColor('#F8F9FA')
.borderRadius(8)
.padding(8)
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildAchievementsView() {
Column({ space: 20 }) {
Text('運動成就')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.margin({ top: 20 })
if (this.achievements.length === 0) {
this.BuildEmptyState('暫無成就', '開始運動解鎖成就吧!')
} else {
Grid() {
ForEach(this.achievements, (achievement: Achievement) => {
GridItem() {
this.BuildAchievementItem(achievement)
}
})
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr')
.columnsGap(15)
.rowsGap(15)
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.padding(20)
}
@Builder BuildAchievementItem(achievement: Achievement) {
Column({ space: 10 }) {
Text(achievement.unlocked ? '🏆' : '🔒')
.fontSize(24)
Text(achievement.title)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.textAlign(TextAlign.Center)
Text(achievement.description)
.fontSize(12)
.fontColor('#666666')
.textAlign(TextAlign.Center)
.maxLines(2)
if (!achievement.unlocked) {
Text(`進度: ${achievement.progress}%`)
.fontSize(10)
.fontColor('#999999')
}
}
.width('100%')
.height(120)
.padding(15)
.backgroundColor(achievement.unlocked ? '#E7F3FF' : '#F8F9FA')
.borderRadius(12)
}
@Builder BuildEmptyState(title: string, message: string) {
Column({ space: 15 }) {
Text('📊')
.fontSize(48)
.opacity(0.5)
Text(title)
.fontSize(18)
.fontColor('#666666')
Text(message)
.fontSize(14)
.fontColor('#999999')
.textAlign(TextAlign.Center)
}
.width('100%')
.height(300)
.justifyContent(FlexAlign.Center)
}
@Builder BuildStatistics() {
Column({ space: 15 }) {
Text('數據統計')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#1A1A1A')
.alignSelf(ItemAlign.Start)
Row({ space: 15 }) {
this.BuildStatCard('平均步數', this.getAverageSteps().toString())
this.BuildStatCard('總距離', this.getTotalDistance().toFixed(1) + 'km')
}
.width('100%')
Row({ space: 15 }) {
this.BuildStatCard('總卡路里', this.getTotalCalories().toString())
this.BuildStatCard('運動天數', this.getWorkoutDays().toString())
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
@Builder BuildStatCard(title: string, value: string) {
Column({ space: 8 }) {
Text(value)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#4A90E2')
Text(title)
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
}
.layoutWeight(1)
.padding(15)
.backgroundColor('#F8F9FA')
.borderRadius(12)
}
private loadSampleData(): void {
// 加載示例數據
this.workoutHistory = [
{ date: '2024-01-15', steps: 12560, distance: 8.2, calories: 420, duration: 90, type: 'walking' },
{ date: '2024-01-14', steps: 9870, distance: 6.5, calories: 320, duration: 75, type: 'running' },
{ date: '2024-01-13', steps: 11340, distance: 7.8, calories: 380, duration: 85, type: 'walking' }
];
this.achievements = [
{ id: '1', title: '起步者', description: '首次記錄運動', unlocked: true, progress: 100 },
{ id: '2', title: '萬步達人', description: '單日步數超過10000', unlocked: true, progress: 100 },
{ id: '3', title: '運動健將', description: '連續7天運動', unlocked: false, progress: 60 },
{ id: '4', title: '馬拉松', description: '累計跑步100公里', unlocked: false, progress: 25 }
];
// 更新今日數據
this.updateTodayData();
}
private startStepSimulation(): void {
// 模擬步數更新
setInterval(() => {
if (this.currentView === 'dashboard') {
this.addSteps(Math.floor(Math.random() * 10) + 1);
}
}, 5000);
}
private addSteps(steps: number): void {
this.stepCount += steps;
this.distance += steps * 0.0007; // 假設步幅0.7米
this.calories += Math.floor(steps * 0.04); // 假設每步消耗0.04卡路里
this.updateTodayData();
}
private addWorkout(type: string, steps: number, duration: number): void {
const distance = type === 'running' ? steps * 0.001 : steps * 0.0007;
const calories = Math.floor(steps * (type === 'running' ? 0.08 : 0.04));
this.stepCount += steps;
this.distance += distance;
this.calories += calories;
const today = new Date().toISOString().split('T')[0];
this.workoutHistory.push({
date: today,
steps: steps,
distance: distance,
calories: calories,
duration: duration,
type: type
});
this.updateTodayData();
}
private updateTodayData(): void {
const today = new Date().toISOString().split('T')[0];
const todayRecord = this.workoutHistory.find(record => record.date === today);
if (todayRecord) {
todayRecord.steps = this.stepCount;
todayRecord.distance = this.distance;
todayRecord.calories = this.calories;
} else {
this.workoutHistory.push({
date: today,
steps: this.stepCount,
distance: this.distance,
calories: this.calories,
duration: 0,
type: 'walking'
});
}
this.checkAchievements();
}
private checkAchievements(): void {
// 檢查並更新成就狀態
this.achievements.forEach(achievement => {
if (!achievement.unlocked) {
switch(achievement.id) {
case '3':
achievement.progress = this.getConsecutiveDays();
if (achievement.progress >= 7) achievement.unlocked = true;
break;
case '4':
achievement.progress = Math.min(this.getTotalDistance(), 100);
if (achievement.progress >= 100) achievement.unlocked = true;
break;
}
}
});
}
private getHealthTip(): string {
if (this.stepCount < 5000) {
return '今天運動量較少,建議多走動,可以嘗試每小時站起來活動5分鐘。';
} else if (this.stepCount < 10000) {
return '繼續保持!適當增加運動量有助於提高新陳代謝。';
} else {
return '很棒!你已達成今日目標,繼續保持健康的生活習慣。';
}
}
private getWorkoutIcon(type: string): string {
switch(type) {
case 'running': return '🏃';
case 'walking': return '🚶';
default: return '🏃';
}
}
private getAverageSteps(): number {
if (this.workoutHistory.length === 0) return 0;
const total = this.workoutHistory.reduce((sum, record) => sum + record.steps, 0);
return Math.floor(total / this.workoutHistory.length);
}
private getTotalDistance(): number {
return this.workoutHistory.reduce((sum, record) => sum + record.distance, 0);
}
private getTotalCalories(): number {
return this.workoutHistory.reduce((sum, record) => sum + record.calories, 0);
}
private getWorkoutDays(): number {
return this.workoutHistory.length;
}
private getConsecutiveDays(): number {
// 簡化實現,返回最近連續運動天數
return Math.min(this.workoutHistory.length, 7);
}
}
class WorkoutRecord {
date: string = '';
steps: number = 0;
distance: number = 0;
calories: number = 0;
duration: number = 0;
type: string = 'walking';
}
class UserProfile {
name: string = '用户';
age: number = 25;
weight: number = 65;
height: number = 170;
dailyStepGoal: number = 10000;
}
class Achievement {
id: string = '';
title: string = '';
description: string = '';
unlocked: boolean = false;
progress: number = 0;
}
想入門鴻蒙開發又怕花冤枉錢?別錯過!現在能免費系統學 -- 從 ArkTS 面向對象核心的類和對象、繼承多態,到吃透鴻蒙開發關鍵技能,還能衝刺鴻蒙基礎 +高級開發者證書,更驚喜的是考證成功還送好禮!快加入我的鴻蒙班,一起從入門到精通,班級鏈接:點擊免費進入