Для одного из текущих проектов понадобилась поддержка акселерометра. Учитывая то, что еще месяц назад Android API я в глаза не видел, мне казалось что получение данных с акселерометра – это какой-то адский труд. Оказалось все намного проще.
Для работы с различными датчиками в Android используется класс Sensor
. Список датчиков можно получить через SensorManager
. Например таким вот образом при создании Activity
можно получить объект Sensor
, связанный с акселеромтером:
public class AccelerometerTest extends Activity { SensorManager mSensorManager; Sensor mAccelerometerSensor; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL); if(sensors.size() > 0) { for (Sensor sensor : sensors) { switch(sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: if(mAccelerometerSensor == null) mAccelerometerSensor = sensor; break; default: break; } } } }
Для того, чтобы получать данные с акселерометра нам необходимо проделать еще несколько несложных операций:
- Реализовать интерфейс
SensorEventListener
- Реализовать метод
onResume()
где подписатьActivity
на сообщения от акселеромтера - Реализовать метод
onPause()
где отписатьActivity
от сообщений акселерометра
, в частности нас интересует метод onSensorChanged()
public class AccelerometerTest extends Activity implements SensorEventListener { @Override protected void onPause() { mSensorManager.unregisterListener(this); super.onPause(); } @Override protected void onResume() { super.onResume(); mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mMagneticFieldSensor, SensorManager.SENSOR_DELAY_GAME); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { float [] values = event.values; switch(event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: { // Здесь можно обрабатывать данные от сенсора } break; } } }
Простейший пример отображения данных – отображать их в TextView
public class AccelerometerTest extends Activity implements SensorEventListener { SensorManager mSensorManager; Sensor mAccelerometerSensor; TextView mForceValueText; TextView mXValueText; TextView mYValueText; TextView mZValueText; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ... mForceValueText = (TextView)findViewById(R.id.value_force); mXValueText = (TextView)findViewById(R.id.value_x); mYValueText = (TextView)findViewById(R.id.value_y); mZValueText = (TextView)findViewById(R.id.value_z); } ... @Override public void onSensorChanged(SensorEvent event) { float [] values = event.values; switch(event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: { mXValueText.setText(String.format("%1.3f", event.values[SensorManager.DATA_X])); mYValueText.setText(String.format("%1.3f", event.values[SensorManager.DATA_Y])); mZValueText.setText(String.format("%1.3f", event.values[SensorManager.DATA_Z])); double totalForce = 0.0f; totalForce += Math.pow( values[SensorManager.DATA_X]/SensorManager.GRAVITY_EARTH, 2.0); totalForce += Math.pow( values[SensorManager.DATA_Y]/SensorManager.GRAVITY_EARTH, 2.0); totalForce += Math.pow( values[SensorManager.DATA_Z]/SensorManager.GRAVITY_EARTH, 2.0); totalForce = Math.sqrt(totalForce); mForceValueText.setText(String.format("%1.3f", totalForce)); } break; } } }
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <TableLayout android:id="@+id/TableLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/row_force" android:layout_margin="5dip"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/label_force" android:text="Force:" android:gravity="right"></TextView> <TextView android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_weight="1" android:id="@+id/value_force" android:text="-" android:layout_marginLeft="5dip"></TextView> </TableRow> <TableRow android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/row_x" android:layout_margin="5dip"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="X:" android:id="@+id/label_x" android:gravity="right"></TextView> <TextView android:layout_height="wrap_content" android:text="-" android:layout_width="fill_parent" android:layout_weight="1" android:id="@+id/value_x" android:layout_marginLeft="5dip"></TextView> </TableRow> <TableRow android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/row_y" android:layout_margin="5dip"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Y:" android:id="@+id/label_y" android:gravity="right"></TextView> <TextView android:layout_height="wrap_content" android:text="-" android:layout_width="fill_parent" android:layout_weight="1" android:id="@+id/value_y" android:layout_marginLeft="5dip"></TextView> </TableRow> <TableRow android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/row_z" android:layout_margin="5dip"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Z:" android:id="@+id/label_z" android:gravity="right"></TextView> <TextView android:layout_height="wrap_content" android:text="-" android:layout_width="fill_parent" android:layout_weight="1" android:id="@+id/value_z" android:layout_marginLeft="5dip"></TextView> </TableRow> </TableLayout>
В результате получим что-то подобное:
Из примера можно увидеть что в классе SensorManager
есть константы DATA_X
, DATA_Y
, DATA_Z
, которые используются в качетсве индексов в массиве значений, возвращаемых акселерометром.
Отображение данных в TextView
– это, конечно, неплохо, но не дает общей картины изменений показаний акселерометра при изменении положения телефона. Для того, чтобы увидеть изменение показаний во времени, решил добавить отображение в виде графика.
Для создания графиков набрел на чудесную библиотеку AChartEngine. Библиотека бесплатная, доступна на Google Code.
Добавляем в layout пару кнопок – для начала/останова записи показаний акселерометра и для открытия окна с графиком.
<?xml version="1.0" encoding="utf-8"?> <TableLayout android:id="@+id/TableLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> ... <TableRow android:id="@+id/TableRow01" android:layout_height="wrap_content" android:layout_width="fill_parent"> <ViewStub android:id="@+id/ViewStub01" android:layout_width="wrap_content" android:layout_height="wrap_content"></ViewStub> <LinearLayout android:id="@+id/LinearLayout01" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_weight="1"> <Button android:layout_height="wrap_content" android:layout_weight="1" android:text="Start recording" android:layout_width="fill_parent" android:id="@+id/button_start"></Button> <Button android:layout_height="wrap_content" android:layout_weight="1" android:text="Show" android:layout_width="fill_parent" android:id="@+id/button_show"></Button> </LinearLayout> </TableRow> </TableLayout>
В результате этих изменений получаем такой layout:
Теперь научим Activity
реагировать на нажания кнопок:
package com.itdimension.accelerometertest; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import org.achartengine.*; import org.achartengine.chart.PointStyle; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; public class AccelerometerTest extends Activity implements SensorEventListener { ... double margins[] = {0, 0}; Button mStartButton; Button mShowButton; List<List<Double>> mValues; boolean mIsRecording = false; OnClickListener mStartButtonListener = new OnClickListener() { @Override public void onClick(View v) { mIsRecording = !mIsRecording; if(mIsRecording) { mValues.get(SensorManager.DATA_X).clear(); mValues.get(SensorManager.DATA_Y).clear(); mValues.get(SensorManager.DATA_Z).clear(); margins[0] = 0; margins[1] = 0; } } }; OnClickListener mShowButtonListener = new OnClickListener() { @Override public void onClick(View v) { try { Intent intent = getChartIntent(); startActivity(intent); } catch (Exception e) { new AlertDialog.Builder(AccelerometerTest.this) .setTitle("Error") .setMessage(e.getMessage()) .create() .show(); } } }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ... mValues = new ArrayList<List<Double>>(); mValues.add(new ArrayList<Double>()); mValues.add(new ArrayList<Double>()); mValues.add(new ArrayList<Double>()); ... mStartButton = (Button)findViewById(R.id.button_start); mShowButton = (Button)findViewById(R.id.button_show); mStartButton.setOnClickListener(mStartButtonListener); mShowButton.setOnClickListener(mShowButtonListener); } ... @Override public void onSensorChanged(SensorEvent event) { float [] values = event.values; switch(event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: { if(mIsRecording) { recordSensorValue(event); } ... } break; } } private void recordSensorValue(SensorEvent event) { double value; for(int i = SensorManager.DATA_X; i <= SensorManager.DATA_Z; i++) { value = (double)event.values[i]; margins[0] = Math.min(margins[0], value); margins[1] = Math.max(margins[1], value); mValues.get(i).add(value); } } Intent getChartIntent() { int [] colors = new int[] { Color.RED, Color.GREEN, Color.BLUE }; PointStyle[] styles = new PointStyle[] { PointStyle.POINT, PointStyle.POINT, PointStyle.POINT }; XYMultipleSeriesRenderer renderer = buildRenderer(colors, styles); setChartSettings(renderer, "Sensor Values", "Index", "Value", 0, mValues.get(SensorManager.DATA_X).size(), margins[0] * 1.5, margins[1] * 1.5, Color.GRAY, Color.LTGRAY); return ChartFactory.getLineChartIntent(this, buildDataset(), renderer); } protected void setChartSettings(XYMultipleSeriesRenderer renderer, String title, String xTitle, String yTitle, double xMin, double xMax, double yMin, double yMax, int axesColor, int labelsColor) { renderer.setChartTitle(title); renderer.setXTitle(xTitle); renderer.setYTitle(yTitle); renderer.setXAxisMin(xMin); renderer.setXAxisMax(xMax); renderer.setYAxisMin(yMin); renderer.setYAxisMax(yMax); renderer.setAxesColor(axesColor); renderer.setLabelsColor(labelsColor); } protected XYMultipleSeriesRenderer buildRenderer(int[] colors, PointStyle[] styles) { XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); int length = colors.length; for (int i = 0; i < length; i++) { XYSeriesRenderer r = new XYSeriesRenderer(); r.setColor(colors[i]); r.setPointStyle(styles[i]); renderer.addSeriesRenderer(r); } return renderer; } XYMultipleSeriesDataset buildDataset() { XYMultipleSeriesDataset result = new XYMultipleSeriesDataset(); XYSeries xSeries = new XYSeries("X"); XYSeries ySeries = new XYSeries("Y"); XYSeries zSeries = new XYSeries("Z"); int count = mValues.get(SensorManager.DATA_X).size(); for(int i = 0; i < count; i++) { xSeries.add(i, mValues.get(SensorManager.DATA_X).get(i)); ySeries.add(i, mValues.get(SensorManager.DATA_Y).get(i)); zSeries.add(i, mValues.get(SensorManager.DATA_Z).get(i)); } result.addSeries(xSeries); result.addSeries(ySeries); result.addSeries(zSeries); return result; } }
После всех этих манипуляций, при нажатии на кнопку “Show” получим приблизительно такой график:
Ну вот, на этом пока все.
Скачать исходный код примера можно здесь.