В своем предыдущем посте я рассказал, как получить углы наклона аппарата во всех трех плоскостях. Однако, как оказалось, метод, использованный в топике является deprecated начиная с API Level 8 (Android 2.2). Исправлю эту ошибку и расскажу, как правильно получать данные под катом.
В документации по Android нам предлагают вместо SENSOR_ORIENTATION использовать метод
Этот метод принимает два параметра:
Значит, для осуществления цели нам всего лишь необходимо получить ту самую матрицу поворота. А получить ее можно из другого метода:
Данный метод в первые два массива помещает матрицу поворота и матрицу отклонения аппарата от (магнитного полюса Земли ?). Для этого ей необходимо также передать данные с датчика акселерометра и геомагнитного датчика. К счастью TYPE_ACCELEROMETER и TYPE_MAGNETIC_FIELD не являются deprecated. Следовательно, с них мы и будем снимать показания.
Layout мы можем взять тот же, что был в прошлом примере. Вот он:
В главной активити объявим переменные:
Метод onCreate должен реализовывать методы класса SensorEventListener, поэтому его объявление изменится:
И добавятся два обязательных метода onAccuracyChanged и onSensorChanged.
А перед setContentView пишем:
Если читали мой прошлый пост, то ничего нового пока не увидели. Если же нет, то Вам нужно знать, что в первой строчке мы получаем объект менеджера датчиков.
Теперь создадим метод onResume, в котором уточним, данные каких датчиков нам необходимы:
Здесь мы передаем в метод registerListener тип нужного нам датчика (полный список обозначений датчиков) и частоту обновления данных (может быть SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME, or SENSOR_DELAY_FASTEST в порядке увеличения частоты).
Чтобы наша программа не съедала ресурсы смартфона будучи свернутой по событию onPause «снимаем» получение данных с датчиков:
Давайте создадим новый метод, в котором данные с датчиков будем заносить в соответствующий датчику массив. Назовем метод loadNewSensorData:
Ну вот, почти и все! Осталось только написать обработчик события onSensorChanged:
Все готово! Теперь я исправил свою ошибку и научил вас получать данные с датчиков не deprecated способом. :)
В заключении приведу ссылки на исходники, на готовый apk и на источник информации.
P.S.: Вы наверняка заметили лишнюю проверку переменных на отличие от null в последнем листинге. Без нее компилятор выдает java.lang.NullPointerException. Буду признателен, если кто-нибудь объяснит или укажет на ошибку.
UPD: Проблема решилась благодаря firexel.
После этого можно убрать лишнюю проверку.
Сначала чуть-чуть теории
В документации по Android нам предлагают вместо SENSOR_ORIENTATION использовать метод
getOrientation (float[] R, float[] values)
Этот метод принимает два параметра:
- R — RotationMatrix или матрица поворота устройства;
- values — массив из трех элементов типа float, в который запишутся углы наклона аппарата в радианах;
Значит, для осуществления цели нам всего лишь необходимо получить ту самую матрицу поворота. А получить ее можно из другого метода:
getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic)
Данный метод в первые два массива помещает матрицу поворота и матрицу отклонения аппарата от (магнитного полюса Земли ?). Для этого ей необходимо также передать данные с датчика акселерометра и геомагнитного датчика. К счастью TYPE_ACCELEROMETER и TYPE_MAGNETIC_FIELD не являются deprecated. Следовательно, с них мы и будем снимать показания.
Переходим к практике
Layout мы можем взять тот же, что был в прошлом примере. Вот он:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="Угол XY" />
<TextView
android:id="@+id/xyValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="0" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView3"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="Угол XZ" /> />
<TextView
android:id="@+id/xzValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="0" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView5"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="Угол ZY" />
<TextView
android:id="@+id/zyValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="25dp"
android:text="0" />
</LinearLayout>
</LinearLayout>
В главной активити объявим переменные:
private final SensorManager msensorManager; //Менеджер сенсоров аппрата
private float[] rotationMatrix; //Матрица поворота
private float[] accelData; //Данные с акселерометра
private float[] magnetData; //Данные геомагнитного датчика
private float[] OrientationData; //Матрица положения в пространстве
private TextView xyView;
private TextView xzView;
private TextView zyView;
Метод onCreate должен реализовывать методы класса SensorEventListener, поэтому его объявление изменится:
public class Main extends Activity implements SensorEventListener{
И добавятся два обязательных метода onAccuracyChanged и onSensorChanged.
А перед setContentView пишем:
msensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
rotationMatrix = new float[16];
accelData = new float[3];
magnetData = new float[3];
OrientationData = new float[3];
xyView = (TextView) findViewById(R.id.xyValue); //
xzView = (TextView) findViewById(R.id.xzValue); // Наши текстовые поля для вывода показаний
zyView = (TextView) findViewById(R.id.zyValue); //
Если читали мой прошлый пост, то ничего нового пока не увидели. Если же нет, то Вам нужно знать, что в первой строчке мы получаем объект менеджера датчиков.
Теперь создадим метод onResume, в котором уточним, данные каких датчиков нам необходимы:
@Override
protected void onResume() {
super.onResume();
msensorManager.registerListener(this, msensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI );
msensorManager.registerListener(this, msensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI );
}
Здесь мы передаем в метод registerListener тип нужного нам датчика (полный список обозначений датчиков) и частоту обновления данных (может быть SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME, or SENSOR_DELAY_FASTEST в порядке увеличения частоты).
Чтобы наша программа не съедала ресурсы смартфона будучи свернутой по событию onPause «снимаем» получение данных с датчиков:
@Override
protected void onPause() {
super.onPause();
msensorManager.unregisterListener(this);
}
Давайте создадим новый метод, в котором данные с датчиков будем заносить в соответствующий датчику массив. Назовем метод loadNewSensorData:
private void loadNewSensorData(SensorEvent event) {
final int type = event.sensor.getType(); //Определяем тип датчика
if (type == Sensor.TYPE_ACCELEROMETER) { //Если акселерометр
accelData = event.values.clone();
}
if (type == Sensor.TYPE_MAGNETIC_FIELD) { //Если геомагнитный датчик
magnetData = event.values.clone();
}
}
Ну вот, почти и все! Осталось только написать обработчик события onSensorChanged:
public void onSensorChanged(SensorEvent event) {
loadNewSensorData(event); // Получаем данные с датчика
SensorManager.getRotationMatrix(rotationMatrix, null, accelData, magnetData); //Получаем матрицу поворота
SensorManager.getOrientation(rotationMatrix, OrientationData); //Получаем данные ориентации устройства в пространстве
if((xyView==null)||(xzView==null)||(zyView==null)){ //Без этого работать отказалось.
xyView = (TextView) findViewById(R.id.xyValue);
xzView = (TextView) findViewById(R.id.xzValue);
zyView = (TextView) findViewById(R.id.zyValue);
}
//Выводим результат
xyView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[0]))));
xzView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[1]))));
zyView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[2]))));
}
Все готово! Теперь я исправил свою ошибку и научил вас получать данные с датчиков не deprecated способом. :)
В заключении приведу ссылки на исходники, на готовый apk и на источник информации.
P.S.: Вы наверняка заметили лишнюю проверку переменных на отличие от null в последнем листинге. Без нее компилятор выдает java.lang.NullPointerException. Буду признателен, если кто-нибудь объяснит или укажет на ошибку.
UPD: Проблема решилась благодаря firexel.
Переставьте местами setContentView и строчки с findViewById в onCreate
После этого можно убрать лишнюю проверку.