鸿蒙开发案例:水平仪
![](https://img2024.cnblogs.com/blog/468667/202411/468667-20241107220027212-698873935.gif)
【1】引言(完整代码在最后面)
高仿纯血鸿蒙Next的水平仪效果。主要功能包括:
1. 倾斜角度检测:通过注册加速度传感器事件监听器,实时获取设备的前后倾斜角度(pitch)和左右倾斜角度(roll)。
2. 角度计算与更新:根据传感器数据计算新的倾斜角度,如果新角度与旧角度的变化超过设定的阈值,则更新状态变量 pitch 和 roll,并计算出当前的综合角度 angle。
3. UI 展示:
• 显示当前角度值的文本。
• 模拟水平仪背景的圆环,包括外部大圆、中间圆和刻度线。
• 动态显示一个小球,其位置根据设备的倾斜角度进行调整,通过动画效果实现平滑移动。
【2】环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:mate60 pro
语言:ArkTS、ArkUI
权限:ohos.permission.ACCELEROMETER(允许应用读取加速度传感器的数据权限)
【3】算法分析
1. 计算俯仰角和横滚角:
使用 atan 函数计算俯仰角和横滚角。atan 函数的输入是Y轴和X、Z轴的平方和的平方根,输出是弧度值,乘以 (180 / Math.PI) 转换为度数。
1 2 | const newPitch = Math.atan(data.y / Math.sqrt(data.x * data.x + data.z * data.z)) * (180 / Math.PI); const newRoll = Math.atan(data.x / Math.sqrt(data.y * data.y + data.z * data.z)) * (180 / Math.PI); |
2. 判断角度变化:
使用 Math.abs 计算新旧角度的差值,判断是否超过设定的阈值。如果超过,则更新当前的俯仰角、横滚角和当前角度。
1 2 3 4 5 | if (Math.abs(newPitch - this .pitch) > this .threshold || Math.abs(newRoll - this .roll) > this .threshold) { this .pitch = newPitch; this .roll = newRoll; this .angle = Math.hypot(newPitch, newRoll); } |
3. 计算小球位置
根据当前角度计算半径,并使用 Math.atan2 计算角度的弧度。然后使用 Math.cos 和 Math.sin 计算小球在圆周上的X和Y坐标。
1 2 3 4 5 | const radius = ( this .angle <= 10 ? this .angle * 2.5 : (10 * 2.5 + ( this .angle - 10))) * UNIT_LENGTH; const angleInRadians = Math.atan2( this .pitch, this .roll); const x = -radius * Math.cos(angleInRadians); const y = radius * Math.sin(angleInRadians); |
【完整代码】
1 配置允许应用读取加速度传感器的数据权限
路径:src/main/module.json5
1 2 3 4 5 6 7 8 | { "module" : { "requestPermissions" : [ { "name" : "ohos.permission.ACCELEROMETER" } ], |
2 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | // 导入传感器服务模块 import { sensor } from '@kit.SensorServiceKit' ; // 导入业务错误处理模块 import { BusinessError } from '@kit.BasicServicesKit' ; // 定义单位长度,用于后续计算中的比例尺 const UNIT_LENGTH = 4; // 使用 @Entry 和 @Component 装饰器定义一个名为 LevelMeter 的组件 @Entry @Component struct LevelMeter { // 定义状态变量,用于存储当前角度 @State angle: number = 0; // 定义状态变量,用于存储前后倾斜角度(pitch) @State pitch: number = 0; // 定义状态变量,用于存储左右倾斜角度(roll) @State roll: number = 0; // 定义角度变化阈值,用于判断是否需要更新角度 private threshold: number = 1; // 组件即将出现时的生命周期方法 aboutToAppear(): void { // 获取传感器列表 sensor.getSensorList((error: BusinessError) => { if (error) { // 如果获取传感器列表失败,输出错误信息 console.error( '获取传感器列表失败' , error); return ; } // 开始更新方向信息 this .startOrientationUpdates(); }); } // 启动方向更新的方法 private startOrientationUpdates(): void { // 注册加速度传感器事件监听器 sensor.on(sensor.SensorId.ACCELEROMETER, (data) => { // 计算新的前后倾斜角度(pitch) const newPitch = Math.atan(data.y / Math.sqrt(data.x * data.x + data.z * data.z)) * (180 / Math.PI); // 计算新的左右倾斜角度(roll) const newRoll = Math.atan(data.x / Math.sqrt(data.y * data.y + data.z * data.z)) * (180 / Math.PI); // 如果新的角度变化超过阈值,则更新状态 if (Math.abs(newPitch - this .pitch) > this .threshold || Math.abs(newRoll - this .roll) > this .threshold) { this .pitch = newPitch; this .roll = newRoll; // 更新当前角度 this .angle = Math.hypot(newPitch, newRoll); } }, { interval: 100000000 }); // 设置更新间隔为100毫秒 } // 计算小球位置的方法 private calculateBallPosition(): Position { // 根据当前角度计算半径 const radius = ( this .angle <= 10 ? this .angle * 2.5 : (10 * 2.5 + ( this .angle - 10))) * UNIT_LENGTH; // 将角度转换为弧度 const angleInRadians = Math.atan2( this .pitch, this .roll); // 计算小球在圆周上的X坐标 const x = -radius * Math.cos(angleInRadians); // 计算小球在圆周上的Y坐标 const y = radius * Math.sin(angleInRadians); // 返回小球的位置 return { x, y }; } // 构建UI的方法 build() { // 使用Stack布局容器 Stack() { // 显示当前角度的文本 Stack() { Row({ space: 5 }) { // 创建一个行布局,设置间距为5 Text(`${Math.floor( this .angle)}°`) // 显示当前角度 .layoutWeight(1) // 设置布局权重 .textAlign(TextAlign.Center) // 文本对齐方式 .fontColor( '#dedede' ) // 文本颜色 .fontSize(60); // 文本大小 }.width( '100%' ).margin({ top: 80 }) // 设置宽度和上边距 }.height( '100%' ).align(Alignment.Top) // 设置高度和对齐方式 // 模拟水平仪背景的圆环 Stack() { // 外部大圆 Text() .width( '600lpx' ) // 设置宽度 .height( '600lpx' ) // 设置高度 .borderRadius( '50%' ) // 设置圆角 .backgroundColor( "#171b1e" ) // 设置背景颜色 .blur(50) // 设置模糊效果 .shadow({ radius: 300, type: ShadowType.COLOR, color: "#232426" }); // 设置阴影效果 // 中间圆 Text() .width(`${( this .angle <= 10 ? this .angle * 2.5 : (10 * 2.5 + ( this .angle - 10))) * UNIT_LENGTH * 2}lpx`) // 计算宽度 .height(`${( this .angle <= 10 ? this .angle * 2.5 : (10 * 2.5 + ( this .angle - 10))) * UNIT_LENGTH * 2}lpx`) // 计算高度 .borderRadius( '50%' ) // 设置圆角 .backgroundColor( "#2e3235" ) // 设置背景颜色 .animation({ curve: Curve.EaseOut }); // 设置动画效果 // 刻度线 ForEach([10, 20, 30, 40, 50], (item: number) => { Text() .width(`${(15 + item) * UNIT_LENGTH * 2}lpx`) // 计算宽度 .height(`${(15 + item) * UNIT_LENGTH * 2}lpx`) // 计算高度 .borderWidth(1) // 设置边框宽度 .borderRadius( '50%' ) // 设置圆角 .borderColor( "#807d8184" ); // 设置边框颜色 // 刻度数字 Text(`${item}`) .width(20) // 设置宽度 .fontColor( "#7d8184" ) // 设置字体颜色 .fontSize(10) // 设置字体大小 .textAlign(TextAlign.Center) // 设置文本对齐方式 .margin({ left: `${(15 + item) * UNIT_LENGTH * 2}lpx` }); // 设置左边距 }); // 小球 Text() .width(14) // 设置宽度 .height(14) // 设置高度 .backgroundColor( "#ff5841" ) // 设置背景颜色 .borderRadius( '50%' ); // 设置圆角 // 小球背景 Stack() { Stack() { } .radialGradient({ center: [20, 20], // 设置渐变中心点 radius: 60, // 设置渐变半径 colors: [ [ "#807a7a83" , 0.0], // 渐变颜色1 [ "#8074747e" , 0.7], // 渐变颜色2 [ "#80898992" , 1.0] // 渐变颜色3 ] }).width( '100%' ).height( '100%' ) // 设置宽度和高度 } .clip( true ) // 设置裁剪 .radialGradient({ center: [20, 20], // 设置渐变中心点 radius: 60, // 设置渐变半径 colors: Math.floor( this .angle) === 0 ? [[ "#FF6B6B" , 0.0], [ "#D84315" , 0.3], [ "#C62828" , 1.0]] : // 当角度为0时的渐变颜色 [[ "#bcbcc6" , 0.0], [ "#464650" , 0.3], [ "#474750" , 1.0]] // 当角度不为0时的渐变颜色 }) .width(50) // 设置宽度 .height(50) // 设置高度 .borderRadius( '50%' ) // 设置圆角 .translate({ x: `${ this .calculateBallPosition().x}lpx`, // 设置X轴偏移 y: `${ this .calculateBallPosition().y}lpx` // 设置Y轴偏移 }) .animation({ curve: Curve.EaseOut }); // 设置动画效果 }.width( '580lpx' ).height( '580lpx' ).borderRadius( '50%' ) // 设置外部大圆的宽度、高度和圆角 }.height( '100%' ) // 设置Stack的高度 .width( '100%' ) // 设置Stack的宽度 .backgroundColor( "#18181a" ); // 设置背景颜色 } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步