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


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

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

Д.

Доступна запись вебкаста – Palm webOS Application Basics

Доступна запись вебкаста “Palm WebOS: Application Basics“, который проходил 9 сентября 2009г. Скачать можно здесь.

Описание

This session begins with an overview of the basic webOS application structure and a demo of the core SDK developer tools, and includes detail presentations on the application launch lifecycle, and Mojo controllers and methods. We’ll create the initial version of News application with a single story view which will use many of the elements described in this segment including appinfo.json, sources.json, basic scene styles, stage and scene controllers, pushScene and swapScene methods among others.

П.

Презентация первого Android-устройства, HTC Dream, состоится 23го сентября

Презентация HTC Dream, превого Linux-смартфона на платформе Google Android анонсирована на 23е сентября. Мероприятие, проводимое оператором сотовой связи T-Mobile, состоится в Нью-Йорке.

По данным Wall Street Journal, T-Mobile начнет продажи HTC Dream во второй половине октября этого года. HTC планирует продать в этом году до 700 тысяч устройств HTC Dream.
Для тех разработчиков мобильных приложений, которые уже начали разработку для Goole Android, появление первго устройства для этой платформы может стать неплохим подспорьем в процессе отладки и тестирования своих приложений.

Для тех же, кто еще только думает начать разрабатывать ПО для Android-устройств, думаю, будут интересны следующие ссылки: