鸿蒙APP开发-带你走进核心谱的读取传感器数据

1 阅读6分钟

如果你想在鸿蒙App里读取传感器数据,@ohos.sensor怎么用

如果你对普拉提训练有兴趣,可以去鸿蒙应用市场搜一下**「核心谱」**,下载下来体验体验。它能检测你的动作幅度,告诉你每个动作做得够不够到位。体验完了再回来看这篇文章,你会更清楚加速度计数据是怎么获取和处理的。


写在前面

大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,CSS3动画、requestAnimationFrame、Web Animation API这些都算是看家本领。去年开始转战鸿蒙生态,用ArkTS开发App,这一路踩了不少坑,也积累了不少心得。

很多人觉得"前端转鸿蒙"应该很容易——都是写UI嘛,组件化、状态管理、生命周期,概念都差不多。但真正上手之后你会发现,相似的地方让你觉得亲切,不同的地方让你抓狂

比如:

  • 传感器:Web里用DeviceMotionEventDeviceOrientationEvent,通过addEventListener监听;鸿蒙里用@ohos.sensor,通过sensor.on订阅。思路类似,但API完全不同。
  • 权限管理:Web里传感器API大部分浏览器不需要特殊权限;鸿蒙里需要声明权限并动态请求。
  • 数据格式:Web的DeviceMotionEvent给出的是accelerationIncludingGravity;鸿蒙的加速度计给出的是三轴加速度分量。

但别担心,核心思想是一样的:都是订阅传感器事件、获取三轴数据、处理和分析。你之前积累的前端经验,在鸿蒙里依然是你的核心竞争力。


这篇文章聊什么

核心谱这个App需要读取手机的加速度计数据,来检测用户做普拉提动作时的身体倾斜角度和运动幅度。

对应到HarmonyOS的API,核心是@ohos.sensor——鸿蒙的传感器服务。


第一步:了解鸿蒙的传感器类型

鸿蒙支持多种传感器,常用的有:

传感器SensorId用途
加速度计sensor.SensorId.ACCELEROMETER检测设备加速度
陀螺仪sensor.SensorId.GYROSCOPE检测设备旋转角速度
环境光sensor.SensorId.AMBIENT_LIGHT检测环境光照度
磁力计sensor.SensorId.MAGNETIC_FIELD检测磁场方向
计步器sensor.SensorId.PEDOMETER计步

对于核心谱的动作检测,我们主要用加速度计


第二步:订阅加速度计数据

React版本用Web的DeviceMotionEvent:

// React版本 - 监听加速度
function useAccelerometer(callback) {
  useEffect(() => {
    const handleMotion = (event) => {
      const { x, y, z } = event.accelerationIncludingGravity;
      callback({ x: x || 0, y: y || 0, z: z || 0 });
    };

    window.addEventListener('devicemotion', handleMotion);
    return () => window.removeEventListener('devicemotion', handleMotion);
  }, [callback]);
}

ArkTS版本用@ohos.sensor

import { sensor } from '@kit.SensorServiceKit';

@Entry
@Component
struct AccelerometerPage {
  @State accelX: number = 0
  @State accelY: number = 0
  @State accelZ: number = 0
  @State isListening: boolean = false

  aboutToDisappear() {
    // 页面销毁时取消订阅
    this.stopListening()
  }

  startListening() {
    // 检查传感器是否可用
    if (!sensor.isSensorActive(sensor.SensorId.ACCELEROMETER)) {
      console.error('加速度计不可用')
      return
    }

    // 订阅加速度计数据
    // 参数:传感器ID、采样周期(微秒)
    sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
      this.accelX = data.x
      this.accelY = data.y
      this.accelZ = data.z
    }, { interval: 100000 })  // 100ms = 100000微秒

    this.isListening = true
  }

  stopListening() {
    if (this.isListening) {
      sensor.off(sensor.SensorId.ACCELEROMETER)
      this.isListening = false
    }
  }

  build() {
    Column() {
      Text('加速度计数据')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')

      // 三轴数据显示
      Column() {
        this.AxisDisplay('X', this.accelX, '#EF4444')
        this.AxisDisplay('Y', this.accelY, '#10B981')
        this.AxisDisplay('Z', this.accelZ, '#3B82F6')
      }
      .margin({ top: 20 })

      // 可视化
      Stack() {
        // 中心点
        Circle()
          .width(8)
          .height(8)
          .fill('#FFFFFF')

        // 加速度方向指示
        Circle()
          .width(20)
          .height(20)
          .fill('#10B981')
          .translate({
            x: this.accelX * 5,  // 放大5倍便于观察
            y: this.accelY * 5
          })
      }
      .width(200)
      .height(200)
      .backgroundColor('#1F2937')
      .borderRadius(100)
      .margin({ top: 30 })

      // 控制按钮
      Button(this.isListening ? '停止' : '开始')
        .onClick(() => {
          if (this.isListening) {
            this.stopListening()
          } else {
            this.startListening()
          }
        })
        .margin({ top: 30 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#111827')
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  AxisDisplay(label: string, value: number, color: string) {
    Row() {
      Text(label)
        .fontSize(16)
        .fontColor(color)
        .width(30)

      Text(value.toFixed(2))
        .fontSize(16)
        .fontColor('#FFFFFF')
        .fontFamily('monospace')
    }
    .margin({ bottom: 8 })
  }
}

关键点:

  • sensor.on(sensorId, callback, options):订阅传感器数据
  • options.interval:采样周期,单位微秒。100000微秒 = 100ms = 每秒10次
  • sensor.off(sensorId):取消订阅,必须在页面销毁时调用
  • sensor.isSensorActive(sensorId):检查传感器是否可用

第三步:理解加速度计数据

加速度计的数据是三轴加速度,单位是m/s²:

  • x轴:左右方向(设备水平放置时,向右为正)
  • y轴:前后方向(设备竖直放置时,向上为正)
  • z轴:上下方向(垂直屏幕向上为正)

当设备静止时,受到重力影响:

  • 水平放置:x=0, y=0, z≈9.8
  • 竖直放置:x=0, y≈9.8, z=0

所以,通过x和y的值,可以判断设备的倾斜角度


第四步:计算倾斜角度

// 计算倾斜角度(度)
private calculateTiltAngle(x: number, y: number, z: number): { roll: number, pitch: number } {
  // Roll(左右倾斜):绕Y轴旋转
  const roll = Math.atan2(x, Math.sqrt(y * y + z * z)) * (180 / Math.PI)

  // Pitch(前后倾斜):绕X轴旋转
  const pitch = Math.atan2(y, Math.sqrt(x * x + z * z)) * (180 / Math.PI)

  return { roll, pitch }
}

// 计算运动幅度(加速度的合力)
private calculateMagnitude(x: number, y: number, z: number): number {
  return Math.sqrt(x * x + y * y + z * z)
}

sensor.on的回调里使用:

sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
  this.accelX = data.x
  this.accelY = data.y
  this.accelZ = data.z

  // 计算倾斜角度
  const tilt = this.calculateTiltAngle(data.x, data.y, data.z)
  this.roll = tilt.roll
  this.pitch = tilt.pitch

  // 计算运动幅度
  this.magnitude = this.calculateMagnitude(data.x, data.y, data.z)
}, { interval: 50000 })  // 50ms = 每秒20次

第五步:数据平滑处理

原始的传感器数据会有噪声,直接使用会导致UI抖动。需要用低通滤波器平滑数据。

// 低通滤波器
private smoothValue(current: number, previous: number, alpha: number): number {
  // alpha: 0-1,越小越平滑但延迟越大
  return alpha * current + (1 - alpha) * previous
}

// 在回调里使用
sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
  const alpha = 0.3  // 平滑系数

  this.accelX = this.smoothValue(data.x, this.accelX, alpha)
  this.accelY = this.smoothValue(data.y, this.accelY, alpha)
  this.accelZ = this.smoothValue(data.z, this.accelZ, alpha)
}, { interval: 50000 })

alpha值的选择:

  • alpha = 1:不平滑,直接使用原始数据
  • alpha = 0.5:中等平滑
  • alpha = 0.1:非常平滑,但有明显延迟

对于动作检测,建议alpha = 0.3,既能平滑噪声又不会有太大延迟。


第六步:React vs ArkTS 传感器对比

方面React (Web)ArkTS (HarmonyOS)
APIDeviceMotionEvent@ohos.sensor
订阅方式addEventListenersensor.on
取消订阅removeEventListenersensor.off
数据格式accelerationIncludingGravityAccelerometerResponse
采样频率浏览器控制interval参数指定
权限大部分不需要需要声明权限
可用性检测需要特性检测sensor.isSensorActive

总结

这篇文章围绕"核心谱"的传感器需求,把@ohos.sensor的基础用法过了一遍:

订阅传感器数据

  • sensor.on(sensorId, callback, options):订阅
  • sensor.off(sensorId):取消订阅
  • sensor.isSensorActive(sensorId):检查可用性

加速度计数据

  • 三轴加速度:x(左右)、y(前后)、z(上下)
  • 静止时受到重力影响
  • 通过x/y值计算倾斜角度

数据处理

  • 低通滤波器平滑噪声
  • alpha系数控制平滑程度

注意事项

  • 采样频率不要太高,50-100ms足够
  • 页面销毁时必须取消订阅
  • 原始数据有噪声,需要平滑处理

下一篇文章我会讲核心谱的动作检测逻辑,看看怎么用加速度计数据判断普拉提动作是否到位。