viernes, 14 de noviembre de 2014

Widgets avanzados en Android



Ya hemos visto cómo construir un widget básico para Android, y prometimos que dedicaríamos un apartadoadicional a comentar algunas características más avanzadas de este tipo de componentes. Pues bien, en este segundo apartado sobre el tema vamos a ver cómo podemos añadir los siguientes elementos y funcionalidades al widget básico que ya construimos:

> Pantalla de configuración inicial.
> Datos actualizables de forma periódica.
> Eventos de usuario.

Como sabéis, intento simplificar al máximo todos los ejemplos que utilizo en este curso para que podamos centrar nuestra atención en los aspectos realmente importantes. En esta ocasión utilizaré el mismo criterio, y las únicas características (aunque suficientes para demostrar los tres conceptos anteriores) que añadiremos a nuestro widget serán las siguientes:

1. Añadiremos una pantalla de configuración inicial del widget, que aparecerá cada vez que se añada una nueva instancia del widget a nuestro escritorio. En esta pantalla podrá configurarse únicamente el mensaje de texto a mostrar en el widget.

2. Añadiremos un nuevo elemento de texto al widget que muestre la hora actual. Esto nos servirá para comprobar que el widget se actualiza periódicamente.

3. Añadiremos un botón al widget, que al ser pulsado forzará la actualización inmediata del mismo

Empecemos por el primer punto, la pantalla de configuración inicial del widget. Y procederemos igual que para el diseño de cualquier otra actividad Android, definiendo su layout xml. En nuestro caso será muy sencilla, un cuadro de texto para introducir el mensaje a personalizar y dos botones, uno para aceptar la configuración y otro para cancelar (en cuyo caso el widget no se añade al escritorio). En esta ocasión llamaremos a este layout "widget_config.xml". Veamos cómo queda:


Una vez diseñada la interfaz de nuestra actividad de configuración tendremos que implementar su funcionalidad en java. Llamaremos a la clase WidgetConfig, su estructura será análoga a la de cualquier actividad de Android, y las acciones a realizar serán las comentadas a continuación. En primer lugar nos hará falta el identificador de la instancia concreta del widget que se configurará con esta actividad. Este ID nos llega como parámetro del intent que ha lanzado la actividad. Como ya vimos en un apartado anterior del curso, este intent se puede recuperar mediante el método getIntent() y sus parámetros mediante el método getExtras(). Conseguida la lista de parámetros del intent, obtendremos el valor del ID del widget accediendo a la clave AppWidgetManager.EXTRA_APPWIDGET_ID. Veamos el código hasta este momento: 


En el código también podemos ver como aprovechamos este momento para establecer el resultado por defecto a devolver por la actividad de configuración mediante el método setResult(). Esto es importante porque las actividades de configuración de widgets deben devolver siempre un resultado (RESULT_OK en caso de aceptarse la configuración, o RESULT_CANCELED en caso de salir de la configuración sin aceptar los cambios). Estableciendo aquí ya un resultado RESULT_CANCELED por defecto nos aseguramos de que si el usuario sale de la configuración pulsando el botón Atrás del teléfono no añadiremos el widget al escritorio, mismo resultado que si pulsáramos el botón "Cancelar" de nuestra actividad.

Como siguiente paso recuperamos las referencias a cada uno de los controles de la actividad de configuración:


Por último, implementaremos las acciones de los botones "Aceptar" y "Cancelar". En principio, el botón Cancelar no tendría por qué hacer nada, tan sólo finalizar la actividad mediante una llamada al método finish() ya que el resultado CANCELED ya se ha establecido por defecto anteriormente:


En el caso del botón Aceptar tendremos que hacer más cosas:

1. Guardar de alguna forma el mensaje que ha introducido el usuario.
2. Actualizar manualmente la interfaz del widget según la configuración establecida.
3. Devolver el resultado RESULT_OK aportando además el ID del widget.

Para el primer punto nos ayudaremos de la API de Preferencias (para más información leer el capítulo dedicado a este tema). En nuestro caso, guardaremos una sola preferencia cuya clave seguirá el patrón "msg_IdWidget", esto nos permitirá distinguir el mensaje configurado para cada instancia del widget que añadamos a nuestro escritorio de Android.

El segundo paso indicado es necesario debido a que si definimos una actividad de configuración para un widget, será ésta la que tenga la responsabilidad de realizar la primera actualización del mismo en caso de ser necesario. Es decir, tras salir de la actividad de configuración no se lanzará automáticamente el evento onUpdate() del widget (sí se lanzará posteriormente y de forma periódica según la configuración del parámetro updatePeriodMillis del provider que veremos más adelante), sino que tendrá que ser la propia actividad quien fuerce la primera actualización. Para ello, simplemente obtendremos una referencia al widget manager de nuestro contexto mediente el método AppWidgetManager.getInstance() y con esta referencia llamaremos al método estático de actualización del widget MiWidget. actualizarWidget(), que actualizará los datos de todos los controles del widget (lo veremos un poco más adelante).

Por último, al resultado a devolver (RESULT_OK) deberemos añadir información sobre el ID de nuestro widget. Esto lo conseguimos creando un nuevo Intent que contenga como parámetro el ID del widget que recuperamos antes y estableciéndolo como resultado de la actividad mediante el método setResult(resultado, intent). Por último llamaremos al método finish() para finalizar la actividad. Con estas indicaciones, veamos cómo quedaría el código del botón Aceptar:


Ya hemos terminado de implementar nuestra actividad de configuración. Pero para su correcto funcionamiento aún nos quedan dos detalles más por modificar. En primer lugar tendremos que declarar esta actividad en nuestro fichero AndroidManifest.xml, indicando que debe responder a los mensajes de tipo APPWIDGET_CONFIGURE:


Por último, debemos indicar en el XML de configuración de nuestro widget (xml\miwidget_wprovider. xml) que al añadir una instancia de este widget debe mostrarse la actividad de configuración que hemos creado. Esto se consigue estableciendo el atributo android:configure del provider. Aprovecharemos además este paso para establecer el tiempo de actualización automática del widget al mínimo permitido por este parámetro (30 minutos) y el tamaño del widget a 3×2 celdas. Veamos cómo quedaría finalmente:


Con esto, ya tenemos todo listo para que al añadir nuestro widget al escritorio se muestre automáticamente la pantalla de configuración que hemos construido. Podemos ejecutar el proyecto en este punto y comproba que todo funciona correctamente.

Como siguiente paso vamos a modificar el layout del widget que ya construimos en el apartado anterior para añadir una nueva etiqueta de texto donde mostraremos la hora actual, y un botón que nos servirá para forzar la actualización de los datos del widget:


Hecho esto, tendremos que modificar la implementación de nuestro provider (MiWidget.java) para que en cada actualización del widget se actualicen sus controles con los datos correctos (recordemos que en el apartado anterior dejamos este evento de actualización vacío ya que no mostrábamos datos actualizables en el widget). Esto lo haremos dentro del evento onUpdate() de nuestro provider.

Como ya dijimos, los componentes de un widget se basan en un tipo especial de vistas que llamamos Remote Views. Pues bien, para acceder a la lista de estos componentes que constituyen la interfaz del widget construiremos un nuevo objeto RemoteViews a partir del ID del layout del widget. Obtenida la lista de componentes, tendremos disponibles una serie de métodos set (uno para cada tipo de datos básicos) para establecer las propiedades de cada control del widget. Estos métodos reciben como parámetros el ID del control, el nombre del método que queremos ejecutar sobre el control, y el valor a establecer. Además de estos métodos, contamos adicionalmente con una serie de métodos más específicos para establecer directamente el texto y otras propiedades sencillas de los controles TextView, ImageView, ProgressBar y Chronometer, como por ejemplo setTextViewText(idControl, valor) para establecer el texto de un control TextView.
Pueden consultarse todos los métodos disponibles en la documentación oficial de la clase RemoteViews. De esta forma, si por ejemplo queremos establecer el texto del control cuyo id es LblMensaje haríamos lo siguiente:


El proceso de actualización habrá que realizarlo por supuesto para todas las instancias del widget que se hayan añadido al escritorio. Recordemos aquí que el evento onUpdate() recibe como parámetro la lista de widgets que hay que actualizar.

Dicho esto, creo que ya podemos mostrar cómo quedaría el código de actualización de nuestro widget:


Como vemos, todo el trabajo de actualización para un widget lo hemos extraído a un método estático independiente, de forma que también podamos llamarlo desde otras partes de la aplicación (como hacemos por ejemplo desde la actividad de configuración para forzar la primera actualización del widget).

Además quiero destacar la última línea del código, donde llamamos al método updateAppWidget() del widget manager. Esto es importante y necesario, ya que de no hacerlo la actualización de los controles no se reflejará correctamente en la interfaz del widget.

Tras esto, ya sólo nos queda implementar la funcionalidad del nuevo botón que hemos incluido en el widget para poder forzar la actualización del mismo. A los controles utilizados en los widgets de Android, que ya sabemos que son del tipo RemoteView, no podemos asociar eventos de la forma tradicional que hemos visto en múltiples ocasiones durante el curso. Sin embargo, en su lugar, tenemos la posibilidad de asociar a un evento (por ejemplo, el click sobre un botón) un determinado mensaje (Pending Intent) de tipo broadcast que será lanzado cada vez que se produzca dicho evento. Además, podremos configurar el widget (que como ya indicamos no es más que un componente de tipo broadcast receiver) para que capture esos mensajes, e implementar en su evento onReceive() las acciones necesarias a ejecutar tras capturar el mensaje. Con estas tres acciones simularemos la captura de eventos sobre controles de un widget.

Vamos por partes. En primer lugar hagamos que se lance un intent de tipo broadcast cada vez que se pulse el botón del widget.
Para ello, en el método actualizarWidget() construiremos un nuevo Intent asociándole una acción personalizada, que en nuestro caso llamaremos por ejemplo "net.sgoliver.android.widgets.ACTUALIZAR_WIDGET" . Como parámetro del nuevo Intent insertaremos mediante putExtra() el ID del widget actual de forma que más tarde podamos saber el widget concreto que ha lanzado el mensaje (recordemos que podemos tener varias instancias del mismo widget en el escritorio). Por último crearemos el PendingIntent mediante el método getBroadcast() y lo asociaremos al evento onClick del control llamando a setOnClickPendingIntent() pasándole el ID del control, en nuestro caso el botón de "Actualizar". Veamos cómo queda todo esto dentro del método actualizarWidget():


También podemos hacer por ejemplo que si pulsamos en el resto del espacio del widget (el no ocupado por el botón) se abra automáticamente la actividad principal de nuestra aplicación. Se haría de forma análoga, con la única diferencia que en vez de utilizar getBroadcast() utilizaríamos getActivity() y e Intent lo construiríamos a partir de la clase de la actividad principal:


Ahora vamos a declarar en el Android Manifest este mensaje personalizado, de forma que el widget sea capaz de capturarlo. Para ello, añadiremos simplemente un nuevo elemento <intent-filter> con nuestro nombre de acción personalizado dentro del componente <receiver> que ya teníamos definido:


Por último, vamos a implementar el evento onReceive() del widget para actuar en caso de recibir nuestro mensaje de actualización personalizado. Dentro de este evento comprobaremos si la acción del mensaje recibido es la nuestra, y en ese caso recuperaremos el ID del widget que lo ha lanzado, obtendremos una referencia al widget manager, y por último llamaremos a nuestro método estático de actualización pasándole estos datos.


Con esto, por fin, hemos ya finalizado la construcción de nuestro widget Android y podemos ejecutar el proyecto de Eclipse para comprobar que todo funciona correctamente, tanto para una sola instancia como para varias instancias simultaneas.

Cuando añadamos el widget al escritorio nos aparecerá la pantalla de configuración que hemos definido:

Una vez introducido el mensaje que queremos mostrar, pulsaremos el botón Aceptar y el widget aparecerá automáticamente en el escritorio con dicho mensaje, la fecha-hora actual y el botón Actualizar.


Un comentario final, la actualización automática del widget se ha establecido a la frecuencia mínima que permite el atributo updatePeriodMillis del widget provider, que son 30 minutos. Por tanto es difícil y aburrido esperar para verla en funcionamiento mientras probamos el widget en el emulador. Pero funciona, os lo aseguro. De cualquier forma, esos 30 minutos pueden ser un periodo demasiado largo de tiempo según la funcionalidad que queramos dar a nuestro widget, que puede requerir tiempos de actualización mucho más cortos (ojo con el rendimiento y el gasto de batería). Para solucionar esto podemos hacer uso de Alarmas, pero por ahora no nos preocuparemos de esto.



Saludos compañeros, aprovechen la información.








No hay comentarios:

Publicar un comentario en la entrada