En primer lugar, creamos una actividad AnimacionFotoAspecto.java con el siguiente layout:
La actividad comienza creando la misma animación de fotogramas del ejemplo anterior, pero en lugar de asignarla al background, la asignaremos directamente al ImageView mediante
Esto todavía no resuelve el problema. Si intentamos iniciar la animación, veremos que la imagen no mantiene su relación de aspecto, como se observa en la figura 3.3.1. La razón es que los parámetros del ImageView son fill_parent (recordemos que no podemos usar wrap_content porque no se muestra nada). Un modo de forzar a modificar el tamaño de la imagen sería añadir márgenes mediante setPadding. Para ello tenemos que calcular el tamaño vertical que ocupa la imagen y, a partir de ahí, calcular la anchura que debería tener manteniendo su relación de aspecto.
Figura 3.3.1. Animación de fotogramas que no mantiene la relación de aspecto.
En la siguiente actividad se calcula la relación de aspecto real del primer fotograma. Seguidamente, calculamos la altura que tiene el ImageView en la pantalla mediante getMeasuredHeight. En este punto nos encontramos otro problema, ya que si escribimos estos valores en la pantalla, veremos que el ImageView tiene dimensiones nulas, como se muestra en la figura 3.3.2. La razón es que estas dimensiones se están calculando dentro del método onCreate de la actividad cuando el layout de la actividad no se ha desplegado completamente. Los contenidos solo están definidos cuando la actividad está lista para interaccionar con el usuario; por ejemplo, al pulsar uno de los botones. Por lo tanto, la solución es realizar nuestras manipulaciones dentro del método onClick. El programa Java es el siguiente:
Al calcular dentro de onClick las dimensiones del ImageView, vemos que ya no son cero, por lo que podemos calcular el margen necesario en la imagen para mantener la relación de aspecto (padding). El resultado se muestra en la figura 3.3.2. (izquierda). Hemos escrito las dimensiones del ImageView antes y después de onClick, cuando ya se ha desplegado el layout, así como la nueva anchura de la imagen y el valor necesario del padding. Es ilustrativo pulsar los botones Detener y Comenzar de nuevo, porque entonces se vuelve a calcular las dimensiones del ImageView, que se han modificado respecto al paso anterior al escribir dos líneas de texto. Esto hace recalcular la anchura de la imagen y reajustar el padding, como se observa en la figura 3.3.2. (derecha). Dicha operación puede repetirse varias veces y se verá que la relación de aspecto se mantiene en todos los pasos, hasta que la imagen desaparezca de la pantalla por abajo.
Figura 3.3.2. Animación de fotogramas que mantiene la relación de aspecto usando padding.
4. PROCESOS EN BACKGROUND
En Android podemos ejecutar simultáneamente partes de un programa como procesos paralelos. Cada uno de estos procesos se denomina thread, o hilo, y se ejecuta en background con una prioridad determinada bajo el control de un objeto de la clase Thread de Java. Una forma de crear un hilo es definir una clase que extienda a la clase Thread. La nueva clase debe sobrescribir el método run(), que se ejecutará cuando se inicie el hilo. Otra forma de crear un hilo es implementando la interfaz Runnable. En este capítulo veremos algunos ejemplos del uso de hilos (en el libro Android se pueden ver ejemplos alternativos).
Inicialmente, solo hay un hilo asociado a una actividad, el hilo principal, que consiste en una serie de instrucciones o sentencias que se ejecutan secuencialmente. Además, este hilo es el que controla los elementos de la interfaz de usuario (UI) que se muestran en pantalla. En este capítulo veremos también cómo se puede modificar la UI desde otro hilo.
4.1. Uso de Timer y RunOnUiThread
La clase Timer del paquete java.util permite programar tareas que se ejecutan transcurrido un tiempo, de forma única o secuencialmente. Un temporizador es un objeto de tipo Timer. Para programarlo se utilizan los métodos schedule, para realizar una única tarea, o scheduleAtFixedRate, para una serie repetitiva de tareas. Para detener el temporizador, se usa el método cancel. Por ejemplo, para programar una tarea repetitiva, escribiríamos lo siguiente:
Aquí, retraso es el tiempo de espera hasta que se ejecute la acción; periodo, el tiempo de repetición y timerTask es un objeto de una subclase de TimerTask, que contiene las instrucciones a ejecutar a través de su método run.
El método run de TimerTask se ejecuta en background en su propio hilo. Por lo tanto, no podremos modificar la UI (la pantalla), pues los objetos de esta, por ejemplo un TextView, solo se pueden cambiar desde el hilo principal. Para sincronizar con la UI y modificar sus objetos, podemos usar el método runOnUiThread(Runnable), de la clase Activity. Su argumento es un objeto de tipo Runnable cuyo método run se ejecutará en sincronía con el hilo principal y permitirá modificar los objetos View que en él residan.
La siguiente actividad ilustra el uso de Timer combinado con RunOnUiThread para mostrar un cronómetro que avanza cada 100 milisegundos. Usaremos el siguiente layout:
La actividad Temporizador.java se detalla a continuación. Contiene dos clases internas: Tarea, subclase de TimerTask, y CambiaTexto, que implementa a la interfaz Runnable, para modificar el TextView. No es necesario usar invalidate para redibujar la pantalla. En la figura 4.1. se muestra el resultado.
Figura 4.1. Cronómetro con Timer y RunOnUiThread.
4.2. Modificación de la UI con post
Alternativamente al método runOnUiThread de la clase Activity, podemos usar el método post(Runnable) de la clase View. El argumento de post debe definir un método run que se ejecutará en el mismo hilo que la UI.