Анализ
Видимо, этот код отслеживает ориентацию оси Y телефона, которая становится вертикальной на вертикальном телефоне, и его ориентация на землю направлена к вам, когда телефон наклоняется к вам.
Да, это правильно.Вы можете проверить код 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: вектор, указывающий на телефон front
Векторы описаны с точки зрения системы координат Земли ( восток , север и небо ).
Имея это в виду, мы можем интерпретировать код в getOrientation()
:
- выберите ось телефона вверх (столбец матрицы 1, хранится в элементах массива 1, 4, 7)
- проецируйте его на горизонтальную плоскость Земли (это легко, просто игнорируйтекомпонент sky , хранящийся в элементе 7)
- Используйте
atan2
, чтобы вывести угол из оставшихся восточных и север компоненты вектора.
Здесь скрывается еще одна тонкость: подпись atan2
равна
public static double atan2(double y, double x);
Обратите внимание на порядок параметров: y
, затем x
.Но getOrientation
передает аргументы в порядке восток , север .Это позволяет достичь двух вещей:
- делает север исходной осью (в геометрии это ось
x
) - отражает угол: геометрические углы являютсяпо часовой стрелке, но азимут должен быть углом по часовой стрелке с севера
Естественно, когда ось up телефона переходит в вертикальное положение ("в небо"), а затем за его пределы, его азимут поворачивается на 180 градусов,Мы можем исправить это очень простым способом: вместо этого мы будем использовать правую ось телефона.Обратите внимание на следующее:
- , когда телефон расположен горизонтально и направлен на север, его ось вправо совмещена с осью восток .Ось восток в системе координат Земли является геометрической осью "x", поэтому наша нулевая точка отсчета верна прямо из коробки.
- , когда телефон поворачиваетсявправо (на восток), его азимут должен возрасти, но его геометрический угол становится отрицательным.Поэтому мы должны перевернуть знак геометрического угла.
Решение
Итак, наша новая формула такова:
val azimuth = -atan2(R[3], R[0])
И это тривиальное изменение - это все, что вынеобходимость!Не нужно звонить getOrientation
, просто примените это к матрице ориентации.
Улучшенное решение
Пока все хорошо.Но что, если пользователь использует телефон в альбомной ориентации?На оси телефона это не влияет, но теперь пользователь воспринимает направление «влево» или «вправо» телефона как «вперед» (в зависимости от того, как пользователь повернул телефон).Мы можем исправить это, проверив свойство Display.rotation
.Если экран повернуть, мы будем использовать ось телефона up , чтобы играть ту же роль, что и ось вправо выше.
Таким образом, полный кодслушатель ориентации становится таким:
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.