Sensores en Android: Acelerómetro

Buenas tardes, en esta entrada vamos a ver como podemos obtener valores usando el Acelerómetro de nuestro dispositivo. Habremos visto muchas aplicaciones que usan este Sensor para interactuar con el usuario, en este caso se vé mucho más en juegos, en los que según movamos el dispositivo el juego reacciona de una manera distinta.

En este tutorial voy a mostraros como podemos captar los valores de los ejes X, Y y Z en el dispositivo.


Con esta introducción, vamos a ponernos manos a la obra:

  • Creación del Proyecto

    Aunque no debe de ser ningún secreto para nosotros, vamos a crear un proyecto de aplicación Android. Para ello nos dirigiremos a File->New->Android Application Project. Si por algún casual no encontraramos esta opción en la lista, vamos a Other y en la ventana que nos aparece lo buscaremos, aunque también podremos introducir “Android” en el campo de texto que veremos, filtrando así los resultados disponibles.

    Una vez tengamos localizado “Android Application Project” y pulsemos en el, veremos como se nos ejecuta el asistente de creación del proyecto. En la primera ventana daremos nombre a nuestro proyecto y a nuestra aplicación, en mi caso he llamado al proyecto “EjemploAcelerometro”, aunque podéis poner el que queráis. El resto de opciones podéis dejarlas igual.

    A medida qeu avancemos en el asistente, nos aparecerá una pantalla en la que podremos dar nombre a nuestra primera Activity, la cual en mi caso he llamado “SensorActivity” aunque podéis darle el nombre que veais conveniente.

    Una vez terminado el asistente podremos continuar con la construcción de nuestra aplicación.

  • SensorActivity

    En nuestra Activity vamos a tratar toda la implementación del Sensor que queremos usar, en este caso, el Acelerómetro. Para comenzar, vamos a editar la UI de nuestra actividad, para añadir los campos que veamos necesarios que queremos mostrar, ya que podemos obtener bastantes como veremos mas tarde:

    		<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    		    xmlns:tools="http://schemas.android.com/tools"
    		    android:layout_width="match_parent"
    		    android:layout_height="match_parent"
    		    android:orientation="vertical"
    		    android:padding="10dp"
    		    tools:context=".SensorActivity" >
    		
    		    <!-- Sensor Name -->
    		
    		    <TextView
    		        android:id="@+id/sensorNameLabel"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignParentTop="true"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/sensorNameLabel" />
    		
    		    <TextView
    		        android:id="@+id/sensorNameValue"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/sensorNameLabel"
    		        android:layout_toRightOf="@id/sensorNameLabel" />
    		    <!-- End Sensor Name -->
    		
    		
    		    <!-- Sensor Vendor -->
    		
    		    <TextView
    		        android:id="@+id/sensorVendorLabel"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/sensorNameLabel"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/sensorVendorLabel" />
    		
    		    <TextView
    		        android:id="@+id/sensorVendorValue"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/sensorVendorLabel"
    		        android:layout_toRightOf="@id/sensorVendorLabel" />
    		    <!-- End Sensor Vendor -->
    		
    		
    		    <!-- Sensor Version -->
    		
    		    <TextView
    		        android:id="@+id/sensorVersionLabel"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/sensorVendorLabel"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/sensorVersionLabel" />
    		
    		    <TextView
    		        android:id="@+id/sensorVersionValue"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/sensorVersionLabel"
    		        android:layout_toRightOf="@id/sensorVersionLabel" />
    		    <!-- End Sensor Version -->
    		
    		
    		    <!-- Sensor Power -->
    		
    		    <TextView
    		        android:id="@+id/sensorPowerLabel"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/sensorVersionLabel"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/sensorPowerLabel" />
    		
    		    <TextView
    		        android:id="@+id/sensorPowerValue"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/sensorPowerLabel"
    		        android:layout_toRightOf="@id/sensorPowerLabel" />
    		    <!-- End Sensor Power -->
    		
    		
    		    <!-- Sensor X -->
    		
    		    <TextView
    		        android:id="@+id/labelX"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/sensorPowerLabel"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/Ejex" />
    		
    		    <TextView
    		        android:id="@+id/valueX"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/labelX"
    		        android:layout_marginLeft="5dp"
    		        android:layout_toRightOf="@id/labelX" />
    		    <!-- End SensorX -->
    		
    		
    		    <!-- Sensor Y -->
    		
    		    <TextView
    		        android:id="@+id/labelY"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/labelX"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/Ejey" />
    		
    		    <TextView
    		        android:id="@+id/valueY"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/labelY"
    		        android:layout_marginLeft="5dp"
    		        android:layout_toRightOf="@id/labelY" />
    		    <!-- End Sensor Y -->
    		
    		
    		    <!-- Sensor Z -->
    		
    		    <TextView
    		        android:id="@+id/labelZ"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_below="@id/labelY"
    		        android:layout_marginBottom="5dp"
    		        android:text="@string/Ejez" />
    		
    		    <TextView
    		        android:id="@+id/valueZ"
    		        android:layout_width="wrap_content"
    		        android:layout_height="wrap_content"
    		        android:layout_alignTop="@id/labelZ"
    		        android:layout_marginLeft="5dp"
    		        android:layout_toRightOf="@id/labelZ" />
    		    <!-- End Sensor Z -->
    		
    		</RelativeLayout>
    		

    Como vemos, tenemos una gran multitud de TextView en nuestro XML. Como podemos ver en los comentarios, vamos a capturar el nombre del sensor, el vendedor del sensor, la versión del sensor, la potencia del sensor, y por último 3 TextView que mostrarán los valores reales del sensor en funcionamiento.

    Una vez tengamos definida nuestra UI, vamos a nuestra clase SensorActivity en la cuál implementaremos lo necesario para recoger todos los valores y mostrarlos. A continuación vemos su código, y luego lo analizaremos:

    		package sekth.droid.sensor;
    
    		import android.app.Activity;
    		import android.hardware.Sensor;
    		import android.hardware.SensorEvent;
    		import android.hardware.SensorEventListener;
    		import android.hardware.SensorManager;
    		import android.os.Bundle;
    		import android.util.Log;
    		import android.widget.TextView;
    		
    		public class SensorActivity extends Activity implements SensorEventListener {
    		
    			private SensorManager mSensor;
    			private TextView tvSensorValue, tvVendorValue, tvVersionValue,
    					tvPowerValue, tvValueX, tvValueY, tvValueZ;
    		
    			@Override
    			protected void onCreate(Bundle savedInstanceState) {
    				super.onCreate(savedInstanceState);
    				setContentView(R.layout.activity_sensor);
    		
    				tvSensorValue = (TextView) findViewById(R.id.sensorNameValue);
    				tvVendorValue = (TextView) findViewById(R.id.sensorVendorValue);
    				tvVersionValue = (TextView) findViewById(R.id.sensorVersionValue);
    				tvPowerValue = (TextView) findViewById(R.id.sensorPowerValue);
    		
    				tvValueX = (TextView) findViewById(R.id.valueX);
    				tvValueY = (TextView) findViewById(R.id.valueY);
    				tvValueZ = (TextView) findViewById(R.id.valueZ);
    		
    				mSensor = (SensorManager) getSystemService(SENSOR_SERVICE);
    				mSensor.registerListener(this,
    						mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
    						SensorManager.SENSOR_DELAY_UI);
    		
    				mostrarInformacion();
    		
    			}
    		
    			@Override
    			public void onAccuracyChanged(Sensor sensor, int accuracy) {
    				// TODO Auto-generated method stub
    		
    			}
    		
    			@Override
    			public void onSensorChanged(SensorEvent event) {
    				// TODO Auto-generated method stub
    				if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
    					refreshValues(event);
    				}
    			}
    		
    			private void mostrarInformacion() {
    				// List<Sensor> sensorList =
    				// mSensor.getSensorList(Sensor.TYPE_ACCELEROMETER);
    				Sensor sensor = mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    				tvSensorValue.setText(sensor.getName());
    				tvVendorValue.setText(sensor.getVendor());
    				tvVersionValue.setText(String.valueOf(sensor.getVersion()));
    				tvPowerValue.setText(String.valueOf(sensor.getPower()));
    		
    			}
    		
    			private void refreshValues(SensorEvent event) {
    				float values[] = event.values;
    		
    				float x = values[0];
    				float y = values[1];
    				float z = values[2];
    		
    				tvValueX.setText(String.valueOf(x));
    				Log.i("refreshValues", "Valor del Eje X: " + String.valueOf(x));
    				tvValueY.setText(String.valueOf(y));
    				Log.i("refreshValues", "Valor del Eje Y: " + String.valueOf(y));
    				tvValueZ.setText(String.valueOf(z));
    				Log.i("refreshValues", "Valor del Eje Z: " + String.valueOf(z));
    		
    			}
    		
    			@Override
    			protected void onPause() {
    				// TODO Auto-generated method stub
    				super.onPause();
    				mSensor.unregisterListener(this);
    			}
    		
    			@Override
    			protected void onResume() {
    				// TODO Auto-generated method stub
    				super.onResume();
    				mSensor.registerListener(this,
    						mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
    						SensorManager.SENSOR_DELAY_UI);
    			}
    		
    		}
    		

    Podemos ver que tenemos la clase SensorManager como variable de instancia. Esta clase nos permitirá acceder a los sensores del sistema.

    • protected void onCreate()

      En este método vamos a inicializar todos los componentes de nuestra aplicación. Vamos a instanciar los TextView necesarios para luego establecerle unos valores y vamos a instanciar también la clase SensorManager para poder obtener información y tener acceso a los sensores del sistema:

      				@Override
      				protected void onCreate(Bundle savedInstanceState) {
      					super.onCreate(savedInstanceState);
      					setContentView(R.layout.activity_sensor);
      			
      					tvSensorValue = (TextView) findViewById(R.id.sensorNameValue);
      					tvVendorValue = (TextView) findViewById(R.id.sensorVendorValue);
      					tvVersionValue = (TextView) findViewById(R.id.sensorVersionValue);
      					tvPowerValue = (TextView) findViewById(R.id.sensorPowerValue);
      			
      					tvValueX = (TextView) findViewById(R.id.valueX);
      					tvValueY = (TextView) findViewById(R.id.valueY);
      					tvValueZ = (TextView) findViewById(R.id.valueZ);
      			
      					mSensor = (SensorManager) getSystemService(SENSOR_SERVICE);
      					mSensor.registerListener(this,
      							mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
      							SensorManager.SENSOR_DELAY_UI);
      			
      					mostrarInformacion();
      			
      				}
      				

      La parte mas importante para poder recaudar información sobre el Acelerómetro en este caso, es en la que definimos un Listener en el SensorManager:

      				mSensor = (SensorManager) getSystemService(SENSOR_SERVICE);
      				mSensor.registerListener(this,
      						mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
      						SensorManager.SENSOR_DELAY_UI);
      				

      Con esta línea estamos registrando un listener el cuál acepta como parámetros el Context de nuestra Activity, el sensor que queremos registrar, y una constante que recaudará los datos a una velocidad determinada. Hay diferentes constantes para obtener los datos a una velocidad diferente, que podéis probar perfectamente y sin problemas.

    • public void mostrarInformacion()

      Este método se encargará de establecer en nuestros TextView el nombre del sensor, el vendedor/fabricante, la versión y la potencia del dispositivo:

      				private void mostrarInformacion() {
      					// List<Sensor> sensorList =
      					// mSensor.getSensorList(Sensor.TYPE_ACCELEROMETER);
      					Sensor sensor = mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
      					tvSensorValue.setText(sensor.getName());
      					tvVendorValue.setText(sensor.getVendor());
      					tvVersionValue.setText(String.valueOf(sensor.getVersion()));
      					tvPowerValue.setText(String.valueOf(sensor.getPower()));
      				}
      				
    • public void onSensorChanged(SensorEvent event)

      En este método se ejecutará siempre y cuando cambie algun valor del Sensor. En este caso, si el evento tiene que ver con el Acelerómetro, vamos a refrescar estos valores en los TextView referentes a X, Y y Z:

      				@Override
      				public void onSensorChanged(SensorEvent event) {
      					// TODO Auto-generated method stub
      					if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
      						refreshValues(event);
      					}
      				}
      				
    • private void refreshValues(SensorEvent event)

      En este método extraeremos los valores del evento, y los estableceremos en el TextView:

      				private void refreshValues(SensorEvent event) {
      					float values[] = event.values;
      			
      					float x = values[0];
      					float y = values[1];
      					float z = values[2];
      			
      					tvValueX.setText(String.valueOf(x));
      					Log.i("refreshValues", "Valor del Eje X: " + String.valueOf(x));
      					tvValueY.setText(String.valueOf(y));
      					Log.i("refreshValues", "Valor del Eje Y: " + String.valueOf(y));
      					tvValueZ.setText(String.valueOf(z));
      					Log.i("refreshValues", "Valor del Eje Z: " + String.valueOf(z));
      				}
      				

      Como podemos apreciar, estos valores los extraemos del evento, el cual de manera interna tiene un array de float que almacena el valor de X, y y Z. A continuación extraemos del array cada valor a una variable del tipo float y a continuación los establecemos en su TextView correspondiente. He añadido una salida mediante la clase Log para que podamos ver luego en la consola los valores.

    • protected void onPause()

      Es importante que sustituyamos este método, ya que si no lo hacemos, la aplicación podría drenarnos la batería si por casualidad tuvieramos que salir de la aplicación. El listener seguiría trabajando, y por tanto seguiría extrayendo datos del sensor:

      				@Override
      				protected void onPause() {
      					// TODO Auto-generated method stub
      					super.onPause();
      					mSensor.unregisterListener(this);
      				}
      				

      Es por ello, por lo que dejamos de registrar el listener en el SensorManager para esta actividad cuando la aplicación entre en un estado de pausa.

    • protected void onResume()

      Si nuestra aplicación por alguna razón pasa por el método onPause, dejará de obtener los datos, por lo que si entraramos de nuevo no tendríamos respuesta, es por ello que hay que sustituir este método para volver a extraer la información:

      				@Override
      				protected void onResume() {
      					// TODO Auto-generated method stub
      					super.onResume();
      					mSensor.registerListener(this,
      							mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
      							SensorManager.SENSOR_DELAY_UI);
      				}
      				

      Para ello, volvemos a hacer uso de la clase SensorManager y el método registerListener en el cuál pasaremos como parámetros el Context de la actividad, el tipo de sensor que queremos registrar y la velocidad a la que se extraen los datos.

Ahora ya podemos probar nuestra aplicación, aunque nos encontraremos con el problema de que puede darse el caso de que aparezcan datos estáticos en el Eje Z, y que el eje X e Y intercambien los valores cuando tumbamos el emulador. Es por ello que lo mejor es probarlo en un dispositivo real.

A continuación os muestro una captura de pantalla con datos de la aplicación en un dispositivo real, y un vídeo que graba la salida del Logcat para ver el cambio que reciben los valores de los diferentes ejes.

Capturas de pantalla:

Principal

Video del Logcat y dispositivo real conectado:



Con este tutorial hemos visto los principios básicos que necesitamos saber para poder obtener datos del Acelerómetro, ya queda en nuestras manos como emplear estos valores para dar mucho juego y dar una experiencia especial al usuario.

El código del tutorial podeis descargarlo de aquí.

Sin más, cualquier aporte o corrección es bienvenido!.

Saludos!!

  • Franco

    muy buena tu explicación!!! saludos