Ejemplo: Utilizando el acelerómetro

La aplicación que realizaremos nos permitirá ver en pantalla los valores del acelerómetro y mostrar un aviso cuando se detecte cierto movimiento del teléfono.

Es importante notar que el teléfono cuenta con varios sensores (acelerómetro, orientación, campo magnético, luz, etc.) y de ellos vamos a utilizar únicamente uno el del acelerómetro. Un acelerómetro es un sensor que mide la aceleración relativa a la caída libre como marco de referencia.

Trabajar con el acelerometro

En este caso, nuestra interfaz de usuario será muy sencilla por lo que no utilizaremos ningún código base.

Diseño

Mostraremos los valores de X, Y y Z del acelerómetro, para ello necesitamos 3 etiquetas TextView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<TextView
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Valor de X"
	    android:id="@+id/txtAccX"
	    />
	<TextView
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Valor de Y"
	    android:id="@+id/txtAccY"
	    />
	<TextView
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Valor de Z"
	    android:id="@+id/txtAccZ"
	    />
</LinearLayout>

Agregando código

La clase de la Activity principal para nuestra aplicación implementará SensorEventListener para el manejo de las actualizaciones de los sensores, en nuestro caso específico nos interesa el acelerómetro.

public class Main extends Activity implements SensorEventListener

Utilizaremos algunas variables de instancia para el control de los valores de los 3 ejes que mide el acelerómetro (valor anterior y valor actual) y otras para guardar el timestamp de la última actualización y la última vez que se detectó movimiento.

private long last_update = 0, last_movement = 0;
private float prevX = 0, prevY = 0, prevZ = 0;
private float curX = 0, curY = 0, curZ = 0;

Al implementar esta interface debemos sobrecargar dos métodos:

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}

@Override
public void onSensorChanged(SensorEvent event) {}

Vamos a trabajar sobre onSensorChanged nuestro código será un synchronized statement (más información en la documentación de Java) para evitar problemas de concurrencia al estar trabajando con los sensores.

synchronized (this) {
}

A partir del evento recibido como parámetro vamos a obtener el timestamp de la fecha/hora actual (se usará más adelante) y los valores para los 3 ejes del acelerómetro (X, Y, Z).

long current_time = event.timestamp;
            
curX = event.values[0];
curY = event.values[1];
curZ = event.values[2];

Luego, revisamos si este código ya se ha ejecutado alguna vez, es decir, si las variables que guardan los valores anteriores de X, Y y Z tiene valores diferentes de cero. Esto debería ejecutarse sólo la primera vez.

if (prevX == 0 && prevY == 0 && prevZ == 0) {
    last_update = current_time;
    last_movement = current_time;
    prevX = curX;
    prevY = curY;
    prevZ = curZ;
}

Obtenemos la diferencia entre la última actualización y el timestamp actual, esto nos servirá no solo para ver que si existe una diferencia de tiempos en las mediciones si no también para calcular el movimiento. Para ello, tomamos la posición actual (con las 3 coordenadas) y la restamos a la posición anterior, puede ser que el movimiento sea en distintas direcciones por eso nos es útil el valor absoluto.

long time_difference = current_time - last_update;
if (time_difference > 0) {
    float movement = Math.abs((curX + curY + curZ) - (prevX - prevY - prevZ)) / time_difference;
   ...
}

Para decidir en que momento mostramos un aviso Toast indicando el movimiento vamos a usar como valor de frontera de movimiento mínimo 1×10^-6; este valor es arbitrario mientras mayor sea, se necesitará más movimiento y mientras menor sea más sensible será el display del aviso.

Manejamos también 2 variables para el control de tiempo, una para saber la última actualización (last_update) y otra para conocer la última vez que se registró movimiento last_movement y en esta parte del código actualizamos su valor según sea conveniente.

int limit = 1500;
float min_movement = 1E-6f;
if (movement > min_movement) {
    if (current_time - last_movement >= limit) {                    	
        Toast.makeText(getApplicationContext(), "Hay movimiento de " + movement, Toast.LENGTH_SHORT).show();
    }
    last_movement = current_time;
}

Por último actualizamos los valores de X, Y y Z anteriores para la próxima vez que se registre cambio en los sensores.

prevX = curX;
prevY = curY;
prevZ = curZ;
last_update = current_time;

También actualizamos los valores de los 3 ejes del acelerómetro en las etiquetas para visualizarlo en pantalla.

((TextView) findViewById(R.id.txtAccX)).setText("Acelerómetro X: " + curX);
((TextView) findViewById(R.id.txtAccY)).setText("Acelerómetro Y: " + curY);
((TextView) findViewById(R.id.txtAccZ)).setText("Acelerómetro Z: " + curZ);

El código completo queda de la siguiente forma:

@Override
public void onSensorChanged(SensorEvent event) {
    synchronized (this) {
    	long current_time = event.timestamp;
        
        curX = event.values[0];
        curY = event.values[1];
        curZ = event.values[2];
        
        if (prevX == 0 && prevY == 0 && prevZ == 0) {
            last_update = current_time;
            last_movement = current_time;
            prevX = curX;
            prevY = curY;
            prevZ = curZ;
        }

        long time_difference = current_time - last_update;
        if (time_difference > 0) {
            float movement = Math.abs((curX + curY + curZ) - (prevX - prevY - prevZ)) / time_difference;
            int limit = 1500;
            float min_movement = 1E-6f;
            if (movement > min_movement) {
                if (current_time - last_movement >= limit) {                    	
                    Toast.makeText(getApplicationContext(), "Hay movimiento de " + movement, Toast.LENGTH_SHORT).show();
                }
                last_movement = current_time;
            }
            prevX = curX;
            prevY = curY;
            prevZ = curZ;
            last_update = current_time;
        }
        
        
        ((TextView) findViewById(R.id.txtAccX)).setText("Acelerómetro X: " + curX);
        ((TextView) findViewById(R.id.txtAccY)).setText("Acelerómetro Y: " + curY);
        ((TextView) findViewById(R.id.txtAccZ)).setText("Acelerómetro Z: " + curZ);
    }	
}

Una vez listo esto, es necesario registrar y anular el registro del Listener para el sensor según corresponda, esto lo haremos en los métodos onResume y onStop de la actividad (más información del ciclo de vida de las Activities en la documentación oficial)

Para el registro, obtenemos primero el servicio del sistema de sensores para asignarlo en un SensorManager y a partir de él obtenemos el acceso al acelerómetro. Al realizar el registro del acelerómetro es necesario indicar una tasa de lectura de datos, en nuestro caso vamos a utilizar SensorManager.SENSOR_DELAY_GAME que es la velocidad mínima para que el acelerómetro pueda usarse en un juego.

@Override
protected void onResume() {
    super.onResume();
    SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
    List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);        
    if (sensors.size() > 0) {
    	sm.registerListener(this, sensors.get(0), SensorManager.SENSOR_DELAY_GAME);
    }
}

Para anular el registro únicamente es necesario indicar que la clase actual (que implementa a SensorEventListener) ya no está interesada en recibir actualizaciones de sensores.

@Override
protected void onStop() {
	SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);    	
    sm.unregisterListener(this);
    super.onStop();
}

Para terminar, en el método onCreate de la Activity vamos a bloquear la orientación para que al mover el teléfono la pantalla no se mueva y la interfaz permanezca tal como la vemos.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);        
    this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

Descargar:

Puedes descargar el código de la aplicación completa y funcional en: Trabajar con el acelerómetro.

Conclusión

En esta entrega del curso Android hemos aprendido a trabajar con el acelerómetro, uno de los varios sensores incluídos en los teléfonos, a través de un Listener, nuestro código de respuesta al evento se ejecuta cuando hay algún cambio en los sensores y detectamos cuando hay cierto movimiento.