Curso Android: Trabajar con el acelerómetro
En el sexto capítulo del Curso Android seguiremos trabajando con el hardware, ahora nos corresponde aprovechar el acelerómetro y usarlo para detectar movimiento del teléfono.
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.
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.
[…] Utilización de sensores […]
[…] Utilización de sensores […]
Hola,
Gracias por tu ejemplo. Me lo he instalado en mi dispositivo pero tarda mucho en aparecer el mensaje de alerta avisando de cuanto se ha movido. ¿Esto es debido a la variable: int limit = 1500 ?
¿Como puedo hacer para que cuando haya un movimiento mayor a 1 se deje de utilizar el sensor?
Mi idea es hacer que cuando haya movimiento en el dispositivo se ejecute un archivo de sonido y deje de leer datos del sensor, ya que ahora mismo lo que me ocurre es que como constantemente se leen datos, se ejecuta el archivo N veces colapsando la aplicación.
¿Quizás si añado estas lineas después de un if para comprobar que el movimiento es mayor a 1 daria resultado?:
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
sm.unregisterListener(this);
super.onStop();
Muchas gracias.
I just hope wehvoer writes these keeps writing more!
S9M8s0 bpxbdtxwqhvr
El ejemplo es muy útil, básico pero muy claro, gracias por el curso