11、传感器坐标系:Android设备坐标系定义、如何将传感器数据映射到屏幕方向

说到霍尔传感器,有个绕不开的话题——坐标系。我刚开始接触Android传感器时,就踩过这个坑。明明传感器数据读出来了,但屏幕上的指针就是乱转。后来才发现,是坐标系没搞对。

说白了,Android定义了一套固定的坐标系,不管你的手机怎么拿,传感器数据都基于这个坐标系。但屏幕显示呢?它得跟着用户转。这两者之间怎么对齐?这就是我们今天要聊的核心。

Android设备坐标系长什么样?

Android官方文档里定义得很清楚。我习惯用右手法则来记:

  • X轴:水平向右。手机平放时,从左边指向右边。
  • Y轴:垂直向上。手机平放时,从底部指向顶部。
  • Z轴:垂直于屏幕向外。手机平放时,从屏幕指向天花板。

嗯,这里要注意:这个坐标系是固定在设备硬件上的。不管你把手机横着拿、竖着拿、倒着拿,坐标系都不变。传感器报告的数据,永远基于这个硬件坐标系。

核心概念:Android传感器坐标系 = 设备硬件坐标系,与屏幕显示方向无关。

我在项目中遇到过一个问题:把手机平放在桌上,Z轴朝上。这时候读磁场数据,Z分量应该是垂直向下的地磁场分量。但如果你把手机翻过来,Z轴就朝下了,数据符号会反过来。这个细节很容易被忽略。

用SVG画一张坐标系图

光说可能不够直观。我画了一张图,把Android设备坐标系和屏幕方向的关系展示出来:

Android设备坐标系与屏幕方向映射 屏幕 X Y Z(穿出屏幕) O 竖屏(默认) Y轴向上 X轴向右 横屏(旋转90°) X轴向上 Y轴向左 映射 传感器数据基于硬件坐标系 → 通过旋转矩阵映射到屏幕方向

传感器数据怎么映射到屏幕方向?

你想想看,用户拿着手机,屏幕可能竖着、横着、甚至倒着。但传感器报告的数据,始终基于硬件坐标系。如果不做映射,就会出现「手机横过来,指针反而指歪了」的情况。

Android提供了SensorManager.getRotationMatrix()SensorManager.remapCoordinateSystem()这两个方法来解决这个问题。我个人的习惯是:

  1. 先获取旋转矩阵:通过加速度计和磁力计数据,计算出设备相对于地球坐标系的旋转矩阵。
  2. 再映射到屏幕方向:用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()把前者映射到后者,才能得到正确的显示结果。

好了,坐标系映射这块就讲到这里。你只要记住:传感器数据是「死」的,屏幕方向是「活」的。把死的映射到活的上面,数据才能用对地方。