【鸿蒙开发实战】HarmonyOS健康计步器

代码功能概述

实现了一个功能完整的鸿蒙健康计步器应用,全面展示了ArkTS在传感器数据获取、数据持久化、图表展示和健康算法计算等方面的核心能力。主要功能包括:

· 实时步数监测:模拟获取用户行走步数,实时更新显示

· 健康数据统计:计算消耗卡路里、行走距离、运动时间等健康指标

· 目标进度追踪:设置每日步数目标,显示完成进度

· 历史数据图表:使用柱状图展示最近7天的步数趋势

· 数据持久化存储:模拟本地存储健康数据,支持历史记录查询

· 智能提醒功能:根据运动状态提供健康建议和提醒

代码逻辑分析

应用采用"多状态驱动UI"的复杂架构设计:

1. 初始化阶段:应用启动时,通过aboutToAppear()生命周期方法加载历史数据和初始化传感器

2. 状态管理:使用多个@State装饰器管理步数数据、健康指标、历史记录和用户设置

3. 实时数据流:

   · 模拟传感器数据 → 更新当前步数 → 重新计算健康指标

   · 数据变化 → 自动保存到本地存储 → 更新图表显示

4. 用户交互流程:

   · 点击重置按钮 → 清零当日步数 → 重新开始统计

   · 修改目标设置 → 更新进度计算 → 刷新界面显示

   · 查看历史记录 → 切换数据显示 → 更新图表内容

5. 智能计算:基于步数自动计算卡路里、距离、时间等衍生健康数据

完整代码

// 数据模型定义
class StepData {
  date: string = '';
  steps: number = 0;
  calories: number = 0;
  distance: number = 0;
}

class UserSettings {
  goal: number = 10000;
  weight: number = 70; // 默认体重70kg
  strideLength: number = 0.7; // 默认步幅0.7米
}

@Entry
@Component
struct StepCounterTutorial {
  // 状态管理变量
  @State currentSteps: number = 0;
  @State dailyGoal: number = 10000;
  @State caloriesBurned: number = 0;
  @State distanceWalked: number = 0;
  @State activeTime: number = 0;
  @State isTracking: boolean = true;
  @State currentDate: string = '';
  @State stepHistory: StepData[] = [];
  @State showHistory: boolean = false;

  // 生命周期函数
  aboutToAppear() {
    this.initializeStepCounter();
    this.startStepTracking();
    this.loadHistoricalData();
  }

  // 初始化计步器
  initializeStepCounter() {
    // 设置当前日期
    const now = new Date();
    this.currentDate = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
    
    // 初始化健康指标
    this.calculateHealthMetrics();
  }

  // 启动步数跟踪
  startStepTracking() {
    // 模拟传感器数据更新
    setInterval(() => {
      if (this.isTracking) {
        // 模拟步数增加
        this.currentSteps += Math.floor(Math.random() * 10);
        this.calculateHealthMetrics();
        this.saveCurrentData();
      }
    }, 3000);
  }

  // 计算健康指标
  calculateHealthMetrics() {
    // 计算卡路里 (步数 * 0.04 * 体重kg)
    this.caloriesBurned = Math.round(this.currentSteps * 0.04 * 70);
    
    // 计算距离 (步数 * 步幅 / 1000)
    this.distanceWalked = parseFloat((this.currentSteps * 0.7 / 1000).toFixed(2));
    
    // 计算活跃时间 (步数 / 100 * 10分钟)
    this.activeTime = Math.round(this.currentSteps / 100 * 10);
  }

  // 加载历史数据
  loadHistoricalData() {
    // 模拟加载历史数据
    for (let i = 6; i >= 0; i--) {
      const date = new Date();
      date.setDate(date.getDate() - i);
      const dateStr = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
      
      this.stepHistory.push({
        date: dateStr,
        steps: Math.floor(Math.random() * 15000),
        calories: Math.floor(Math.random() * 600),
        distance: parseFloat((Math.random() * 10).toFixed(2))
      });
    }
  }

  // 保存当前数据
  saveCurrentData() {
    // 模拟数据持久化
    console.log('保存数据:', {
      steps: this.currentSteps,
      calories: this.caloriesBurned,
      distance: this.distanceWalked
    });
  }

  // 重置步数
  resetSteps() {
    this.currentSteps = 0;
    this.calculateHealthMetrics();
  }

  // 切换历史视图
  toggleHistoryView() {
    this.showHistory = !this.showHistory;
  }

  // 主构建函数
  build() {
    Column({ space: 0 }) {
      // 应用标题和日期
      this.BuildHeader()

      // 主要数据展示
      if (this.showHistory) {
        this.BuildHistoryView()
      } else {
        this.BuildMainDashboard()
      }

      // 底部导航
      this.BuildNavigation()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F7FA')
  }

  // 顶部标题构建函数
  @Builder BuildHeader() {
    Column({ space: 10 }) {
      Text('健康计步器')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#2D3748')

      Text(this.currentDate)
        .fontSize(16)
        .fontColor('#718096')

      // 目标进度条
      this.BuildGoalProgress()
    }
    .width('100%')
    .padding(20)
    .backgroundColor('#FFFFFF')
    .shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: 1 })
  }

  // 目标进度构建函数
  @Builder BuildGoalProgress() {
    const progress = Math.min(this.currentSteps / this.dailyGoal, 1);

    Column({ space: 8 }) {
      Row({ space: 10 }) {
        Text('每日目标')
          .fontSize(14)
          .fontColor('#4A5568')
          .layoutWeight(1)

        Text(`${this.currentSteps} / ${this.dailyGoal}`)
          .fontSize(14)
          .fontColor('#2D3748')
          .fontWeight(FontWeight.Medium)
      }

      // 进度条背景
      Stack() {
        // 背景轨道
        Rect()
          .width('100%')
          .height(8)
          .fill('#E2E8F0')
          .borderRadius(4)

        // 进度填充
        Rect()
          .width(`${progress * 100}%`)
          .height(8)
          .fill(progress >= 1 ? '#48BB78' : '#4299E1')
          .borderRadius(4)
      }
      .width('100%')
      .height(8)
    }
    .width('100%')
    .margin({ top: 10 })
  }

  // 主仪表盘构建函数
  @Builder BuildMainDashboard() {
    Column({ space: 20 }) {
      // 步数环形进度
      this.BuildStepCircle()

      // 健康数据统计
      this.BuildHealthStats()

      // 控制按钮
      this.BuildControlButtons()
    }
    .width('100%')
    .padding(20)
    .layoutWeight(1)
  }

  // 步数环形进度构建函数
  @Builder BuildStepCircle() {
    const progress = Math.min(this.currentSteps / this.dailyGoal, 1);
    const circumference = 2 * Math.PI * 80; // 环形周长

    Stack({ alignContent: Alignment.Center }) {
      // 背景环
      Circle({ width: 180, height: 180 })
        .fill('#FFFFFF')
        .stroke('#E2E8F0')
        .strokeWidth(12)

      // 进度环
      Circle({ width: 180, height: 180 })
        .fill(Color.Transparent)
        .stroke(progress >= 1 ? '#48BB78' : '#4299E1')
        .strokeWidth(12)
        .strokeDashArray(`${circumference}`)
        .strokeDashOffset(`${circumference * (1 - progress)}`)

      // 中心文本
      Column({ space: 5 }) {
        Text(this.currentSteps.toString())
          .fontSize(36)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2D3748')

        Text('步')
          .fontSize(16)
          .fontColor('#718096')
      }
    }
    .width(200)
    .height(200)
  }

  // 健康统计构建函数
  @Builder BuildHealthStats() {
    Grid() {
      GridItem() {
        this.BuildStatItem('🔥', '卡路里', `${this.caloriesBurned} kcal`)
      }
      GridItem() {
        this.BuildStatItem('👣', '距离', `${this.distanceWalked} km`)
      }
      GridItem() {
        this.BuildStatItem('⏱️', '时间', `${this.activeTime} 分钟`)
      }
      GridItem() {
        this.BuildStatItem('🎯', '完成度', `${Math.round((this.currentSteps / this.dailyGoal) * 100)}%`)
      }
    }
    .columnsTemplate('1fr 1fr')
    .rowsTemplate('1fr 1fr')
    .columnsGap(15)
    .rowsGap(15)
    .width('100%')
  }

  // 统计项构建函数
  @Builder BuildStatItem(icon: string, label: string, value: string) {
    Column({ space: 8 }) {
      Text(icon)
        .fontSize(24)

      Text(label)
        .fontSize(12)
        .fontColor('#718096')

      Text(value)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#2D3748')
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: 1 })
  }

  // 控制按钮构建函数
  @Builder BuildControlButtons() {
    Row({ space: 15 }) {
      Button('重置步数')
        .fontSize(16)
        .fontColor('#FFFFFF')
        .backgroundColor('#E53E3E')
        .borderRadius(25)
        .width(120)
        .height(45)
        .onClick(() => {
          this.resetSteps()
        })

      Button('暂停/继续')
        .fontSize(16)
        .fontColor(this.isTracking ? '#2D3748' : '#FFFFFF')
        .backgroundColor(this.isTracking ? '#E2E8F0' : '#4299E1')
        .borderRadius(25)
        .width(120)
        .height(45)
        .onClick(() => {
          this.isTracking = !this.isTracking
        })
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }

  // 历史视图构建函数
  @Builder BuildHistoryView() {
    Column({ space: 20 }) {
      Text('最近7天步数趋势')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#2D3748')
        .width('100%')
        .textAlign(TextAlign.Start)

      // 柱状图容器
      Column({ space: 10 }) {
        ForEach(this.stepHistory, (item: StepData, index: number) => {
          this.BuildHistoryBar(item, index)
        })
      }
      .width('100%')
      .height(300)
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: 1 })
    }
    .width('100%')
    .padding(20)
    .layoutWeight(1)
  }

  // 历史数据柱状图构建函数
  @Builder BuildHistoryBar(item: StepData, index: number) {
    const maxSteps = Math.max(...this.stepHistory.map(data => data.steps));
    const heightPercent = (item.steps / maxSteps) * 100;

    Row({ space: 15 }) {
      Text(item.date.split('-').slice(1, 3).join('/'))
        .fontSize(12)
        .fontColor('#718096')
        .width(60)

      // 柱状图
      Stack({ alignContent: Alignment.BottomStart }) {
        // 背景
        Rect()
          .width(30)
          .height(150)
          .fill('#E2E8F0')
          .borderRadius(4)

        // 数据柱
        Rect()
          .width(30)
          .height(`${heightPercent}%`)
          .fill('#4299E1')
          .borderRadius(4)
      }
      .width(30)
      .height(150)

      Column({ space: 2 }) {
        Text(item.steps.toString())
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .fontColor('#2D3748')

        Text('步')
          .fontSize(10)
          .fontColor('#718096')
      }
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height(60)
  }

  // 底部导航构建函数
  @Builder BuildNavigation() {
    Row({ space: 0 }) {
      Button('今日数据')
        .fontSize(16)
        .fontColor(this.showHistory ? '#718096' : '#4299E1')
        .backgroundColor(Color.Transparent)
        .width('50%')
        .height(50)
        .onClick(() => {
          this.showHistory = false
        })

      Button('历史记录')
        .fontSize(16)
        .fontColor(this.showHistory ? '#4299E1' : '#718096')
        .backgroundColor(Color.Transparent)
        .width('50%')
        .height(50)
        .onClick(() => {
          this.showHistory = true
        })
    }
    .width('100%')
    .backgroundColor('#FFFFFF')
    .shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: -1 })
  }
}

想入门鸿蒙开发又怕花冤枉钱?别错过!现在能免费系统学 -- 从 ArkTS 面向对象核心的类和对象、继承多态,到吃透鸿蒙开发关键技能,还能冲刺鸿蒙基础 +高级开发者证书,更惊喜的是考证成功还送好礼!快加入我的鸿蒙班,一起从入门到精通,班级链接:点击免费进入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值