Для одного из текущих проектов понадобилась поддержка акселерометра. Учитывая то, что еще месяц назад 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
, в частности нас интересует метод onSensorChanged()
- Реализовать метод
onResume()
где подписать Activity
на сообщения от акселеромтера
- Реализовать метод
onPause()
где отписать Activity
от сообщений акселерометра
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” получим приблизительно такой график:


Ну вот, на этом пока все.
Скачать исходный код примера можно здесь.
Если не секрет, что за приложение?