鸿蒙App开发--星轨算的方向传感器:DeviceSensor与方位罗盘
·
星轨算的方向传感器:DeviceSensor与方位罗盘
如果你是星轨摄影爱好者,推荐去鸿蒙应用市场搜一下**「星轨算」**,下载体验体验。方位罗盘帮你定位北极星,曝光计算器帮你规划参数,设备清单确保你不忘带东西。体验完再回来看这篇文章,你会更清楚方向传感器和方位罗盘背后是怎么实现的。
写在前面
大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,CSS3动画、requestAnimationFrame、Web Animation API这些都算是看家本领。去年开始转战鸿蒙生态,用ArkTS开发App,这一路踩了不少坑,也积累了不少心得。
很多人觉得"前端转鸿蒙"应该很容易——都是写UI嘛,组件化、状态管理、生命周期,概念都差不多。但真正上手之后你会发现,相似的地方让你觉得亲切,不同的地方让你抓狂。
比如:
- 传感器:Web里用
DeviceOrientationEvent获取设备朝向,鸿蒙里用@ohos.sensor,API完全不同。 - 权限管理:Web里传感器权限比较宽松,鸿蒙里需要声明
ohos.permission.ACCELEROMETER等权限。 - Canvas罗盘:绘制罗盘的逻辑在两个平台基本一致,但事件绑定方式不同。
接下来这篇文章,我会用"星轨算"的实际开发经历,带你看看鸿蒙的传感器API怎么实现方位罗盘——从方向传感器数据获取到Canvas罗盘绘制。
这篇文章聊什么
星轨算这个App,核心要解决两个问题:
- 方位罗盘:用设备传感器获取朝向,在Canvas上绘制罗盘
- 北极星定位:帮助用户找到北极星的位置
对应到HarmonyOS的API,主要涉及:
@ohos.sensor— 方向传感器CanvasRenderingContext2D— 罗盘绘制
第一步:传感器数据获取
Web版本:
// React版本 - 获取设备方向
function useDeviceOrientation() {
const [orientation, setOrientation] = useState({ alpha: 0, beta: 0, gamma: 0 });
useEffect(() => {
const handleOrientation = (event) => {
setOrientation({
alpha: event.alpha, // 0-360,设备绕Z轴旋转
beta: event.beta, // -180到180,设备绕X轴旋转
gamma: event.gamma // -90到90,设备绕Y轴旋转
});
};
window.addEventListener('deviceorientation', handleOrientation);
return () => window.removeEventListener('deviceorientation', handleOrientation);
}, []);
return orientation;
}
ArkTS版本:
import { sensor } from '@kit.SensorServiceKit';
@Entry
@Component
struct CompassPage {
@State heading: number = 0 // 朝向角度(0-360)
@State pitch: number = 0 // 俯仰角
@State isSensorAvailable: boolean = false
aboutToAppear() {
this.startOrientationSensor()
}
aboutToDisappear() {
this.stopOrientationSensor()
}
startOrientationSensor() {
try {
// 检查传感器是否可用
this.isSensorAvailable = sensor.isSensorAvailable(sensor.SensorType.ORIENTATION)
if (!this.isSensorAvailable) {
console.error('方向传感器不可用')
return
}
// 订阅方向传感器数据
sensor.on(sensor.SensorType.ORIENTATION, (data: sensor.OrientationResponse) => {
this.heading = data.x // x是方位角,0-360
this.pitch = data.y // y是俯仰角
}, { interval: 100000000 }) // 100ms更新一次
} catch (err) {
console.error(`传感器启动失败: ${err}`)
}
}
stopOrientationSensor() {
sensor.off(sensor.SensorType.ORIENTATION)
}
build() {
Column() {
Text('方位罗盘')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 罗盘Canvas
Canvas(this.ctx)
.width(250)
.height(250)
.onReady(() => {
this.drawCompass()
})
// 朝向信息
Text(`${Math.round(this.heading)}°`)
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#10B981')
.margin({ top: 16 })
Text(this.getDirectionName(this.heading))
.fontSize(16)
.fontColor('#9CA3AF')
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#111827')
}
private getDirectionName(heading: number): string {
const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
const index = Math.round(heading / 45) % 8;
return directions[index];
}
}
第二步:Canvas罗盘绘制
罗盘的绘制逻辑:
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private drawCompass() {
const ctx = this.ctx
const centerX = 125
const centerY = 125
const radius = 100
ctx.clearRect(0, 0, 250, 250)
// 画外圈
ctx.strokeStyle = '#374151'
ctx.lineWidth = 2
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2)
ctx.stroke()
// 画刻度(每30度一个)
for (let i = 0; i < 360; i += 30) {
const rad = (i - 90) * Math.PI / 180
const inner = i % 90 === 0 ? radius - 20 : radius - 10
ctx.strokeStyle = i % 90 === 0 ? '#D1D5DB' : '#6B7280'
ctx.lineWidth = i % 90 === 0 ? 2 : 1
ctx.beginPath()
ctx.moveTo(
centerX + inner * Math.cos(rad),
centerY + inner * Math.sin(rad)
)
ctx.lineTo(
centerX + radius * Math.cos(rad),
centerY + radius * Math.sin(rad)
)
ctx.stroke()
}
// 画方向标签
const labels = [
{ text: 'N', angle: 0, color: '#EF4444' },
{ text: 'E', angle: 90, color: '#D1D5DB' },
{ text: 'S', angle: 180, color: '#D1D5DB' },
{ text: 'W', angle: 270, color: '#D1D5DB' }
]
labels.forEach(label => {
const rad = (label.angle - 90) * Math.PI / 180
const x = centerX + (radius - 30) * Math.cos(rad)
const y = centerY + (radius - 30) * Math.sin(rad)
ctx.fillStyle = label.color
ctx.font = 'bold 16px sans-serif'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(label.text, x, y)
})
// 画指针(指向当前朝向)
const needleAngle = (this.heading - 90) * Math.PI / 180
ctx.strokeStyle = '#10B981'
ctx.lineWidth = 3
ctx.beginPath()
ctx.moveTo(centerX, centerY)
ctx.lineTo(
centerX + 70 * Math.cos(needleAngle),
centerY + 70 * Math.sin(needleAngle)
)
ctx.stroke()
// 画中心点
ctx.fillStyle = '#10B981'
ctx.beginPath()
ctx.arc(centerX, centerY, 5, 0, Math.PI * 2)
ctx.fill()
}
第三步:北极星定位
北极星(Polaris)几乎正好在地球自转轴的延长线上,所以找到北极星就找到了正北方向。
// 北极星定位指南
const POLARIS_GUIDE = {
name: '北极星(Polaris)',
description: '位于小熊座,几乎正对地球自转轴',
howToFind: [
'找到北斗七星(大熊座的一部分)',
'找到勺口两颗星(天璇和天枢)',
'沿这两颗星连线向外延伸约5倍距离',
'那颗亮星就是北极星'
],
altitude: '等于你所在纬度的高度',
importance: '星轨摄影的圆心就在北极星附近'
};
第四步:曝光计算
星轨摄影的曝光计算比普通摄影更复杂,因为要考虑"500法则":
// 500法则:最大曝光时间 = 500 / 焦距
function calculateMaxExposure(focalLength: number): number {
return 500 / focalLength;
}
// 星轨叠加计算
interface StarTrailCalculation {
singleExposure: number; // 单张曝光时间(秒)
numberOfShots: number; // 拍摄张数
totalDuration: number; // 总时长(分钟)
iso: number;
aperture: string;
}
function calculateStarTrail(
focalLength: number,
trailLength: string, // 'short' | 'medium' | 'long'
iso: number,
aperture: string
): StarTrailCalculation {
const maxExposure = calculateMaxExposure(focalLength);
const durationMap = {
'short': 30, // 30分钟
'medium': 60, // 1小时
'long': 120 // 2小时
};
const totalMinutes = durationMap[trailLength] || 60;
const numberOfShots = Math.ceil(totalMinutes * 60 / maxExposure);
return {
singleExposure: Math.round(maxExposure),
numberOfShots,
totalDuration: totalMinutes,
iso,
aperture
};
}
总结
这篇文章围绕"星轨算"的方向传感器功能,讲解了三个核心主题:
- 方向传感器:用
@ohos.sensor获取设备朝向数据 - Canvas罗盘:绘制方位罗盘,显示当前朝向
- 北极星定位:帮助用户找到星轨摄影的圆心
方向传感器是鸿蒙里比较简单的传感器API,关键是理解x(方位角)、y(俯仰角)、z(翻滚角)三个值的含义。
如果你也是星轨摄影爱好者,希望这篇文章能帮你理解星轨算背后的传感器实现。去鸿蒙应用市场下载体验一下吧,有问题欢迎交流。
更多推荐

所有评论(0)