android - 当用户将手机直立时,方位角读数变为相反

标签 android kotlin

我已经根据我在网上找到的常用建议实现了罗盘读数。我使用 ROTATION_VECTOR 传感器类型,并使用标准 API 调用将其转换为 (azimuth, pitch, roll) 三元组。这是我的代码:

fun Fragment.receiveAzimuthUpdates(
        azimuthChanged: (Float) -> Unit,
        accuracyChanged: (Int) -> Unit
) {
    val sensorManager = activity!!.getSystemService(Context.SENSOR_SERVICE)
            as SensorManager
    val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)!!
    sensorManager.registerListener(OrientationListener(azimuthChanged, accuracyChanged),
            sensor, 10_000)
}

private class OrientationListener(
        private val azimuthChanged: (Float) -> Unit,
        private val accuracyChanged: (Int) -> Unit
) : SensorEventListener {
    private val rotationMatrix = FloatArray(9)
    private val orientation = FloatArray(3)

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
        SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
        SensorManager.getOrientation(rotationMatrix, orientation)
        azimuthChanged(orientation[0])
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
            accuracyChanged(accuracy)
        }
    }
}

当您水平拿着手机时,这会产生非常好的行为,就像您拿一个真正的指南针一样。但是,当您将它像相机一样直立放在您面前时,读数就会崩溃。如果你稍微倾斜它超过直立,它会向你倾斜,方位角会转向相反的方向(突然旋转 180 度)。

显然,这段代码跟踪了手机 y 轴的方向,它在直立手机上变为垂直,当手机向您倾斜时,它的地面方向朝向您。

我可以做些什么来改善这种行为,使其对手机的音调不敏感?

最佳答案

长话短说

向下滚动到“改进的解决方案”以获得固定的 OrientationListener 的完整代码。

分析

Apparently this code tracks the orientation of the phone's y-axis, which becomes vertical on an upright phone, and its ground orientation is towards you when the phone leans towards you.

是的,这是正确的。您可以检查 getOrientation() 的代码以了解发生了什么:

public static float[] getOrientation(float[] R, float[] values) {
    /*
     *   /  R[ 0]   R[ 1]   R[ 2]  \
     *   |  R[ 3]   R[ 4]   R[ 5]  |
     *   \  R[ 6]   R[ 7]   R[ 8]  /
     */
     values[0] = (float) Math.atan2(R[1], R[4]);
     ...

values[0] 是你得到的方位角值。

您可以将旋转矩阵 R 解释为指向设备三个主轴的向量分量:

  • 第 0 列:指向手机右侧的矢量
  • 第 1 列:指向手机向上的矢量
  • 第 2 列:指向手机正面的矢量

矢量是从地球坐标系(天空)的角度描述的。

考虑到这一点,我们可以解释 getOrientation() 中的代码:

  1. 选择手机的up轴(矩阵第1列,存储在数组元素1、4、7中)
  2. 将它投影到地球的水平面上(这个很简单,忽略元素7中存储的sky分量即可)
  3. 使用 atan2 从矢量的剩余 分量推导出角度。

这里还隐藏着另一个微妙之处:atan2 的签名是

public static double atan2(double y, double x);

注意参数顺序:y,然后是x。但是 getOrientation 的顺序传递参数。这实现了两件事:

  • 作为引用轴(在几何学中它是x轴)
  • 镜像角度:几何角度是逆时针的,但方位角必须是从北向顺时针的角度

自然地,当手机的向上轴垂直(“向天空”)然后超出时,它的方位角翻转 180 度。我们可以用一种非常简单的方法解决这个问题:我们将改用手机的 轴。请注意以下几点:

  • 当手机水平并面向北方时,其轴与轴对齐。在地球坐标系中, 轴是“x”几何轴,因此我们的 0 角引用是开箱即用的。
  • 当手机右转(向东)时,它的方位角应该上升,但它的几何角度变为负值。因此必须翻转几何角的符号。

解决方案

所以我们的新公式是这样的:

val azimuth = -atan2(R[3], R[0])

而这个微不足道的改变就是您所需要的!无需调用 getOrientation,只需将其应用于方向矩阵即可。

改进的解决方案

到目前为止,还不错。但是如果用户横向使用手机呢?手机的轴不受影响,但现在用户将手机的“左”或“右”方向感知为“前方”(取决于用户转动手机的方式)。我们可以通过检查 Display.rotation 属性来纠正这个问题。如果屏幕旋转,我们将使用手机的轴发挥与上面的轴相同的作用。

所以定向监听器的完整代码变成了这样:

private class OrientationListener(
        private val activity: Activity,
        private val azimuthChanged: (Float) -> Unit,
        private val accuracyChanged: (Int) -> Unit
) : SensorEventListener {
    private val rotationMatrix = FloatArray(9)

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
        SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
        val (matrixColumn, sense) = when (val rotation = 
                activity.windowManager.defaultDisplay.rotation
        ) {
            Surface.ROTATION_0 -> Pair(0, 1)
            Surface.ROTATION_90 -> Pair(1, -1)
            Surface.ROTATION_180 -> Pair(0, -1)
            Surface.ROTATION_270 -> Pair(1, 1)
            else -> error("Invalid screen rotation value: $rotation")
        }
        val x = sense * rotationMatrix[matrixColumn]
        val y = sense * rotationMatrix[matrixColumn + 3]
        azimuthChanged(-atan2(y, x))
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
            accuracyChanged(accuracy)
        }
    }
}

使用此代码,您将获得与在 Google map 上完全相同的行为。

关于android - 当用户将手机直立时,方位角读数变为相反,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53408642/

相关文章:

Android - 临时模式下的 tcpdump 问题

android - GridView水平间距错误

android - 如何对包裹在 `runOnUiThread` 中的代码进行单元测试?

kotlin - 丢弃返回值以返回单位?

android - 约束布局 : Second view snaps to parent instead of bottom of custom view

Android:为webView设置一个可读的字体大小

android - 从/res/raw获取文件

java - Android java 用菜单按钮切换页面

java - 在 Kotlin 中读取 ZipInputStream

java - 使用 RxJava 同时从本地和远程获取数据