11、传感器坐标系:Android设备坐标系定义、如何将传感器数据映射到屏幕方向
说到霍尔传感器,有个绕不开的话题——坐标系。我刚开始接触Android传感器时,就踩过这个坑。明明传感器数据读出来了,但屏幕上的指针就是乱转。后来才发现,是坐标系没搞对。
说白了,Android定义了一套固定的坐标系,不管你的手机怎么拿,传感器数据都基于这个坐标系。但屏幕显示呢?它得跟着用户转。这两者之间怎么对齐?这就是我们今天要聊的核心。
Android设备坐标系长什么样?
Android官方文档里定义得很清楚。我习惯用右手法则来记:
- X轴:水平向右。手机平放时,从左边指向右边。
- Y轴:垂直向上。手机平放时,从底部指向顶部。
- Z轴:垂直于屏幕向外。手机平放时,从屏幕指向天花板。
嗯,这里要注意:这个坐标系是固定在设备硬件上的。不管你把手机横着拿、竖着拿、倒着拿,坐标系都不变。传感器报告的数据,永远基于这个硬件坐标系。
核心概念:Android传感器坐标系 = 设备硬件坐标系,与屏幕显示方向无关。
我在项目中遇到过一个问题:把手机平放在桌上,Z轴朝上。这时候读磁场数据,Z分量应该是垂直向下的地磁场分量。但如果你把手机翻过来,Z轴就朝下了,数据符号会反过来。这个细节很容易被忽略。
用SVG画一张坐标系图
光说可能不够直观。我画了一张图,把Android设备坐标系和屏幕方向的关系展示出来:
传感器数据怎么映射到屏幕方向?
你想想看,用户拿着手机,屏幕可能竖着、横着、甚至倒着。但传感器报告的数据,始终基于硬件坐标系。如果不做映射,就会出现「手机横过来,指针反而指歪了」的情况。
Android提供了SensorManager.getRotationMatrix()和SensorManager.remapCoordinateSystem()这两个方法来解决这个问题。我个人的习惯是:
- 先获取旋转矩阵:通过加速度计和磁力计数据,计算出设备相对于地球坐标系的旋转矩阵。
- 再映射到屏幕方向:用
remapCoordinateSystem()把传感器数据从硬件坐标系转换到屏幕坐标系。
小技巧:如果你只需要获取设备朝向(比如指南针),直接用SensorManager.getOrientation()就行。它会自动处理坐标系映射。
代码示例:从传感器数据到屏幕方向
下面这段代码,是我在实际项目中用过的。它展示了如何把磁场传感器数据映射到屏幕方向:
// 获取旋转矩阵
float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(
rotationMatrix,
null,
accelerometerValues,
magnetometerValues
);
// 获取屏幕旋转角度
int screenRotation = ((Activity) context).getWindowManager()
.getDefaultDisplay().getRotation();
// 根据屏幕方向重新映射坐标系
float[] remappedMatrix = new float[9];
switch (screenRotation) {
case Surface.ROTATION_0: // 竖屏,默认
// 不需要重新映射
remappedMatrix = rotationMatrix;
break;
case Surface.ROTATION_90: // 横屏,顺时针旋转90°
SensorManager.remapCoordinateSystem(
rotationMatrix,
SensorManager.AXIS_X,
SensorManager.AXIS_Z,
remappedMatrix
);
break;
case Surface.ROTATION_180: // 倒置
SensorManager.remapCoordinateSystem(
rotationMatrix,
SensorManager.AXIS_Z,
SensorManager.AXIS_X,
remappedMatrix
);
break;
case Surface.ROTATION_270: // 横屏,逆时针旋转90°
SensorManager.remapCoordinateSystem(
rotationMatrix,
SensorManager.AXIS_Z,
SensorManager.AXIS_Y,
remappedMatrix
);
break;
}
// 获取映射后的方向
float[] orientation = new float[3];
SensorManager.getOrientation(remappedMatrix, orientation);
float azimuth = (float) Math.toDegrees(orientation[0]); // 方位角
float pitch = (float) Math.toDegrees(orientation[1]); // 俯仰角
float roll = (float) Math.toDegrees(orientation[2]); // 翻滚角
警告:我曾经在项目中直接用了getOrientation()的原始数据,没做屏幕方向映射。结果在横屏模式下,指南针指针偏了90度。这个坑我踩过,你别再踩了。
映射的核心逻辑
说白了,映射就是「坐标系变换」。Android的remapCoordinateSystem()方法,本质上是在做矩阵乘法。它把传感器数据从硬件坐标系,变换到屏幕坐标系。
我习惯用一张表来总结不同屏幕方向下的映射关系:
| 屏幕旋转 | 描述 | 映射后的X轴 | 映射后的Y轴 |
|---|---|---|---|
| ROTATION_0 | 竖屏(默认) | 硬件X轴 | 硬件Y轴 |
| ROTATION_90 | 横屏(顺时针) | 硬件Y轴 | 硬件Z轴 |
| ROTATION_180 | 倒置 | 硬件Z轴 | 硬件X轴 |
| ROTATION_270 | 横屏(逆时针) | 硬件Z轴 | 硬件Y轴 |
你可能会问:为什么ROTATION_90要把Y轴映射到Z轴?嗯,这是因为横屏时,屏幕的「向上」方向变成了设备的「向右」方向。而Z轴是垂直于屏幕的,所以需要把Y轴(原向上方向)映射到Z轴方向。
实际项目中的避坑指南
我做了几年传感器开发,总结了几条经验:
- 不要假设屏幕方向固定:用户随时可能旋转手机。每次读取传感器数据时,都要重新获取屏幕旋转角度。
- 注意坐标系的手性:Android坐标系是右手系。如果你自己写矩阵运算,别搞反了。
- 测试要覆盖所有方向:我曾经只测试了竖屏和横屏,结果在倒置模式下出了bug。后来学乖了,四个方向都测一遍。
一句话总结:传感器数据基于硬件坐标系,屏幕显示基于屏幕坐标系。用remapCoordinateSystem()把前者映射到后者,才能得到正确的显示结果。
好了,坐标系映射这块就讲到这里。你只要记住:传感器数据是「死」的,屏幕方向是「活」的。把死的映射到活的上面,数据才能用对地方。