如果你想在鸿蒙App里读取传感器数据,@ohos.sensor怎么用
如果你对普拉提训练有兴趣,可以去鸿蒙应用市场搜一下**「核心谱」**,下载下来体验体验。它能检测你的动作幅度,告诉你每个动作做得够不够到位。体验完了再回来看这篇文章,你会更清楚加速度计数据是怎么获取和处理的。
写在前面
大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,CSS3动画、requestAnimationFrame、Web Animation API这些都算是看家本领。去年开始转战鸿蒙生态,用ArkTS开发App,这一路踩了不少坑,也积累了不少心得。
很多人觉得"前端转鸿蒙"应该很容易——都是写UI嘛,组件化、状态管理、生命周期,概念都差不多。但真正上手之后你会发现,相似的地方让你觉得亲切,不同的地方让你抓狂。
比如:
- 传感器:Web里用
DeviceMotionEvent和DeviceOrientationEvent,通过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) |
|---|---|---|
| API | DeviceMotionEvent | @ohos.sensor |
| 订阅方式 | addEventListener | sensor.on |
| 取消订阅 | removeEventListener | sensor.off |
| 数据格式 | accelerationIncludingGravity | AccelerometerResponse |
| 采样频率 | 浏览器控制 | interval参数指定 |
| 权限 | 大部分不需要 | 需要声明权限 |
| 可用性检测 | 需要特性检测 | sensor.isSensorActive |
总结
这篇文章围绕"核心谱"的传感器需求,把@ohos.sensor的基础用法过了一遍:
订阅传感器数据
sensor.on(sensorId, callback, options):订阅sensor.off(sensorId):取消订阅sensor.isSensorActive(sensorId):检查可用性
加速度计数据
- 三轴加速度:x(左右)、y(前后)、z(上下)
- 静止时受到重力影响
- 通过x/y值计算倾斜角度
数据处理
- 低通滤波器平滑噪声
alpha系数控制平滑程度
注意事项
- 采样频率不要太高,50-100ms足够
- 页面销毁时必须取消订阅
- 原始数据有噪声,需要平滑处理
下一篇文章我会讲核心谱的动作检测逻辑,看看怎么用加速度计数据判断普拉提动作是否到位。