В своем предыдущем посте я рассказал, как получить углы наклона аппарата во всех трех плоскостях. Однако, как оказалось, метод, использованный в топике является 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
После этого можно убрать лишнюю проверку.