如何测量直立设备的侧向倾斜?常规旋转函数无法匹配需求
解决屏幕线条始终与地平线平行的问题
我完全懂你要实现的效果——不管手机怎么绕垂直屏幕的轴旋转,屏幕上的线条都得牢牢对齐真实世界的地平线。之前用ROTATION_VECTOR搭配getRotationMatrix()、getOrientation()没搞定,大概率是坐标系没转对,毕竟传感器的原生坐标系和屏幕坐标系可不是一回事儿,下面给你捋清楚怎么解决:
核心思路
我们要做的是把真实世界里的水平方向(也就是地平线)映射到当前屏幕的坐标系中,然后根据这个映射结果调整线条的旋转角度。不管手机怎么转,只要能算出真实地平线在屏幕上的投影角度,线条跟着这个角度走就对了。
具体实现步骤
1. 注册旋转向量传感器
旋转向量传感器是检测设备姿态最稳定的选择,先把它注册上:
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); Sensor rotationVecSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); sensorManager.registerListener(this, rotationVecSensor, SensorManager.SENSOR_DELAY_GAME);
2. 处理传感器数据,适配屏幕旋转
传感器返回的旋转矩阵是基于设备原生坐标系的,但屏幕可能有横屏、竖屏等旋转状态,必须先把矩阵转换成适配当前屏幕的版本:
@Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() != Sensor.TYPE_ROTATION_VECTOR) return; float[] rotationMatrix = new float[9]; SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); // 适配当前屏幕的旋转方向 int displayRotation = getWindowManager().getDefaultDisplay().getRotation(); float[] screenAlignedMatrix = new float[9]; switch (displayRotation) { case Surface.ROTATION_0: screenAlignedMatrix = rotationMatrix.clone(); break; case Surface.ROTATION_90: // 将设备坐标系的Y轴映射为屏幕X轴,设备X轴反向映射为屏幕Y轴 SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, screenAlignedMatrix); break; case Surface.ROTATION_180: SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y, screenAlignedMatrix); break; case Surface.ROTATION_270: SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, screenAlignedMatrix); break; }
3. 计算地平线在屏幕上的角度
现在我们把真实世界的水平方向(比如代表正东的向量(1,0,0))转换到屏幕坐标系里,再算出这个向量对应的角度:
// 真实世界的水平方向向量(正东,平行地平线) float[] worldHorizon = {1, 0, 0}; float[] screenHorizon = new float[3]; // 旋转矩阵是设备坐标转世界坐标,所以用转置矩阵(正交矩阵逆等于转置)把世界向量转成屏幕向量 Matrix.multiplyMV(screenHorizon, 0, screenAlignedMatrix, 0, worldHorizon, 0); // 屏幕坐标系的Y轴是向下的,所以要反转Y值后再计算角度 float lineAngle = (float) Math.toDegrees(Math.atan2(-screenHorizon[1], screenHorizon[0])); // 最后把这个角度应用到你的线条上即可 updateLineRotation(lineAngle); }
为什么之前的方法行不通?
你之前用getOrientation()得到的角度是基于世界坐标系的,既没有适配屏幕的旋转状态,也没有和屏幕坐标系做对应,直接拿来转线条自然会出错。通过remapCoordinateSystem把旋转矩阵对齐到屏幕坐标系,再映射真实世界的水平方向,才能得到和屏幕完全匹配的正确角度。
而且这个方法还能处理手机倾斜的情况——哪怕屏幕不是水平的,线条也会跟着真实地平线的投影走,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Magnus
相关产品推荐
相关产品推荐

