AppWidgets Índice 1 AppWidgets..................................................................................................................2 2 Crear un Widget........................................................................................................... 3 3 2.1 Definición XML del Widget....................................................................................3 2.2 Layout del Widget....................................................................................................4 2.3 Implementación de la funcionalidad del Widget..................................................... 5 2.4 Manifest................................................................................................................... 6 Actualización del Widget............................................................................................. 7 3.1 4 RemoteViews...........................................................................................................8 Eventos de actualización.............................................................................................. 8 4.1 Abrir una aplicación...............................................................................................10 5 Servicio de actualización............................................................................................10 6 Actividad de configuración........................................................................................ 11 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets En los escritorios de Android es posible colocar un tipo especial de aplicaciones denominadas Widgets. Estos Widgets permiten mostrar al usuario una información (o una especie de interfaz reducida) sin necesidad de abrir una aplicación. Ocupan un determinado tamaño en el escritorio, por lo que es posible tener en el mismo escritorio colocados a la vez varios de estos elementos. Son muy útiles para mostrar información rápida al usuario, como por ejemplo la hora, los últimos correos, últimos eventos del calendario, etc., y que al pulsar sobre ellos se abra la aplicación completa para poder interactuar. 1. AppWidgets Los widgets, que desde el punto de vista del programador son AppWidgets, son pequeños interfaces de programas Android que permanecen en el escritorio del dispositivo móvil. Para añadir alguno sobra con hacer una pulsación larga sobre un área vacía del escritorio y seleccionar la opción "widget", para que aparezca la lista de todos los que hay instalados y listos para añadir. En la versión 4 de Android para añadir widgets hay que ir al menú de aplicaciones y desde la sección de widgets seleccionar uno y pulsarlo prolongadamente para arrastrarlo a la zona deseada de la pantalla de inicio. Seleccionar un (App) Widget Este es un ejemplo de un widget en el que se muestra un reloj, incluido de serie con Android: 2 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets AppWidget reloj de Android 2. Crear un Widget Para crear un widget se necesitan varios elementos: • Una definición o configuración de los metadatos del Widget en XML. • Un layout para definir su interfaz gráfica. • La implementación de la funcionalidad del Widget (un IntentReceiver). • Declaración del Widget en el Android Manifest de la aplicación. 2.1. Definición XML del Widget Los AppWidgets ocupan un determinado tamaño y se refrescan con una determinada frecuencia, estos son metadatos que hay que declarar en el XML que define el widget. Se puede añadir como nuevo recurso XML de Android, y seleccionar el tipo Widget. Se coloca en la carpeta res/xml/. A continuación se incluye la definición XML guardada en "res/xml/miwidget_conf.xml" de nuestro ejemplo: <?xml version="1.0" encoding="utf-8"? > <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="146dip" android:minHeight="72dip" android:updatePeriodMillis="4320000" android:label="Mi Widget" android:initialLayout="@layout/miwidget_layout" /> Las propiedades que hemos definido en este XML son las siguientes: • minWidth y minHeight: ancho y alto mínimo que ocupará el Widget en el escritorio. • updatePeriodMillis: frecuencia de actualización del Widget en milisegundos. • label: nombre del Widget que se mostrará en el menú de selección de Android. • initialLayout: referencia al layout XML con la interfaz del Widget. • La pantalla de inicio de Android por defecto está dividida en una matriz de 4x4 celdas donde se pueden colocar aplicaciones, accesos directos y Widgets. Los Widgets, como hemos dicho, pueden ocupar más de una celda. Estas celdas tienen una dimensión de 74x74 dip (píxeles independientes del dispositivo). Para indicar el número de celdas que va a ocupar el Widget tenemos que calcular el número de "dip" que va a ocupar. La fórmula para calcular esto es muy sencilla: • ancho_minimo = (num_celdas_ancho * 74) - 2 • alto_minimo = (num_celdas_alto * 74) - 2 Los 2 dip que se le restan son por el borde. Por ejemplo, en nuestro caso, para que ocupe 2 celdas de ancho por 1 celda de alto tenemos que indicar una dimensiones de 146dip x 3 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets 72dip. En los dispositivos donde las dimensiones mínimas que establezcamos no coincidan exactamente con las celdas de la pantalla, el tamaño de nuestro widget será extendido para llenar lo que queda libre de las celdas. La frecuencia de actualización del Widget (updatePeriodMillis) tiene un mínimo permitido: 30 minutos (1800000 milisegundos). Además, si el dispositivo está en reposo cuando se realiza la actualización éste se despertará, por lo que puede que se incremente el gasto de batería si se realiza con mucha frecuencia. Es aconsejable, para este tipo de prácticas, utilizar en su lugar intents programados mediante un AlarmManager (como veremos más adelante). 2.2. Layout del Widget En el XML de configuración de ejemplo, en el campo "initialLayout", se indica que el layout del widget se encuentra en "res/layout/miwidget_layout.xml". Este es el layout que define la interfaz gráfica del Widget, su diseño es análogo al del interfaz de cualquier aplicación normal, pero con unas pequeñas restricciones. Por cuestiones de diseño de Android, relacionadas con seguridad y eficiencia, los únicos componentes gráficos permitidos en el layout del widget son: • Los layouts: FrameLayout, LinearLayout y RelativeLayout. • Los views: TextView, Button, ImageButton, ImageView, ProgressBar, AnalogClock y Chronometer. Es interesante el hecho de que los EditText no están permitidos en el layout del widget. Para conseguir este efecto lo que se suele hacer es utilizar una imagen que imite este view y al hacer click, abrir una actividad semi-transparente por encima que sea la que nos permita introducir el texto. Desde la versión 3.0 de Android los widget cuentan con nuevas características y mayor interactividad. Los nuevos componentes que se pueden utilizar son GridView, ListView, StackView, ViewFlipper, AdapterViewFlipper. Por ejemplo StackView no estaba presente entre los componentes de las versiones anteriores y es un view que permite mostrar "tarjetas" e ir pasándolas hacia delante o hacia atrás, como se muestra a continuación: 4 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets Widget con StackView Un ejemplo de layout podría ser el siguiente, en el que se coloca un reloj analógico y un campo de texto al lado: <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/miwidget" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <AnalogClock android:id="@+id/analogClock1" android:layout_width="45dp" android:layout_height="45dp" /> <TextView android:text="" android:id="@+id/TextView01" android:textSize="9dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:shadowColor="#000000" android:shadowRadius="1.2" android:shadowDx="1" android:shadowDy="1"> </TextView> </LinearLayout> 2.3. Implementación de la funcionalidad del Widget Los AppWidgets no necesitan ninguna actividad, sino que se implementan como IntentReceivers con IntentFilters que detectan intents con acciones de actualización de widget como AppWidget.ACTION_APPWIDGET_UPDATE, DELETED, ENABLED, DISABLED, así como con acciones personalizadas. 5 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets Android provee la clase AppWidgetProvider (clase derivada de BroadcastReceiver) que es una alternativa proporcionada por Android para encapsular el procesamiento de los Intent y proveer de handlers para la actualización, borrado, habilitación y deshabilitación del widget. En esta clase deberemos implementar los mensajes a los que vamos a responder desde nuestro widget, entre los que destacan: • onEnabled(): lanzado cuando se añade al escritorio la primera instancia de un widget. • onUpdate(): lanzado periódicamente cada vez que se debe actualizar un widget. • onDeleted(): lanzado cuando se elimina del escritorio una instancia de un widget. • onDisabled(): lanzado cuando se elimina del escritorio la última instancia de un widget. En la mayoría de los casos, tendremos que implementar como mínimo el evento onUpdate(). El resto de métodos dependerán de la funcionalidad de nuestro widget. En nuestro ejemplo de momento solo vamos a implementar el método "onUpdate", aunque lo dejaremos vacío. A continuación se incluye un ejemplo del código de nuestro Widget: public class MiWidget extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Actualizar el Widget... } } 2.4. Manifest El último paso es declarar el Widget dentro del AndroidManifest.xml de nuestra aplicación. No necesitamos declarar una actividad principal, sino que solamente tendremos que declarar el receptor de intents de nuestro widget: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="es.ua.jtech.av.appwidget"> <uses-sdk android:minSdkVersion="7" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <receiver android:name=".MiWidget" android:label="Frases Widget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <intent-filter> <action android:name="es.ua.jtech.av.ACTION_WIDGET_CLICK" /> </intent-filter> <meta-data android:name="android.appwidget.provider" 6 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets android:resource="@xml/miwidget_conf" /> </receiver> </application> </manifest> En el ejemplo anterior de manifest se declara nuestro Widget como un elemento "receiver" dentro de la sección "application". En la etiqueta "name" se indica el nombre de la clase java con el código del receptor. Además se añaden dos secciones importantes, una sección "meta-data" donde en "resource" se tiene que indicar el fichero XML que contiene la descripción del Widget; y otra sección de "intent-filter" para la captura de eventos. En este ejemplo se capturará el evento de actualización del widget y un evento personalizado "es.ua.jtech.av.ACTION_WIDGET_CLICK" (que veremos más adelante). Llegados a este punto ya es posible probar el Widget, añadir instancias al escritorio, desplazarlo por la pantalla o eliminarlo. 3. Actualización del Widget Para realizar la actualización del Widget tenemos que modificar su fichero de código para completar el método "onUpdate" que habíamos dejado vacío en el ejemplo anterior. Es importante tener en cuenta que es posible que hayan varias instancias de un mismo widget añadidas al escritorio, por lo que tendremos que actualizarlas todas. El método "onUpdate" recibe como parámetro un array con la lista de Widgets que hay que actualizar. Por lo que de forma genérica podríamos realizar la actualización de la forma: @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { //Iteramos la lista de widgets en ejecución for (int i = 0; i < appWidgetIds.length; i++) { //ID del widget actual int widgetId = appWidgetIds[i]; //Actualizamos el widget actual actualizarWidget(context, appWidgetManager, widgetId); } } private void actualizarWidget(Context context, AppWidgetManager appWidgetManager, int widgetId) { // Actualizar un Widget... } Al extraer la actualización a un método estático independiente podemos reutilizar este código para llamarlo desde otras partes de la aplicación, como por ejemplo para realizar la actualización inicial. Para realizar la actualización de las vistas de nuestro Widget necesitamos utilizar una clase especial llamada RemoteViews. A continuación se explica este proceso de 7 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets actualización. 3.1. RemoteViews La clase RemoteViews se utiliza para definir y manipular una jerarquía de views que se encuentra en el proceso de una aplicación diferente. Permite cambiar propiedades y ejecutar métodos. En el caso de los widgets, los views están alojados en un proceso (normalmente perteneciente a la pantalla del Home) y hay que actualizarlos desde el IntentReceiver que definimos para controlar el widget. La actualización de los valores de los views se realiza por medio de los métodos de la clase RemoteViews, tales como .setTextViewText(), .setImageViewBitmap(), etc. En primer lugar crearemos un objeto de tipo RemoteViews al que le pasaremos el identificador del layout de nuestro Widget, con este objeto ya podemos actualizar las vistas de este layout, por ejemplo: RemoteViews updateViews = new RemoteViews( context.getPackageName(), R.layout.miwidget_layout); updateViews.setTextViewText(R.id.TextView01, "Mensaje de prueba"); RemoteViews proporciona métodos para acceder a propiedades como setInt(...), setBoolean(...), setBitmap(...), etc, y métodos para acceder a Views específicos, como setTextViewText(...), setTextColor(...), setImageViewBitmap(...), setChronometer(...), setProgressBar(...), setViewVisibility(...). Una vez definido el cambio a realizar por medio de RemoteViews, tenemos que aplicar estas modificaciones utilizando el appWidgetManager que recibimos como parámetro. Esto es importante, ya que de no hacerlo la actualización de los controles no se reflejará correctamente en la interfaz del widget. En el siguiente código se muestra un ejemplo completo en el que se actualiza el campo de texto del Widget con la hora actual: private void actualizarWidget(Context context, AppWidgetManager appWidgetManager, int widgetId) { RemoteViews updateViews = new RemoteViews( context.getPackageName(), R.layout.miwidget_layout); //Obtenemos la hora actual Calendar calendario = new GregorianCalendar(); String hora = calendario.getTime().toLocaleString(); //Actualizamos la hora en el control del widget updateViews.setTextViewText(R.id.TextView01, hora); //Notificamos al manager de la actualización del widget actual appWidgetManager.updateAppWidget(widgetId, updateViews); } 4. Eventos de actualización 8 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets 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. Sin embargo, tenemos la posibilidad de asociar a un evento (por ejemplo la realización de un click) una determinada acción de tipo broadcast (PendingIntent) que será lanzada cada vez que se produzca dicho evento. Podemos configurar el propio widget para que capture esos mensajes (ya que como indicamos no es más que un componente de tipo broadcast receiver). Para esto tenemos que implementar su método "onReceive()" con las acciones necesarias a ejecutar tras capturar el mensaje. De esta forma "simulamos" la captura de eventos sobre controles de un widget. En primer lugar hacemos que se lance un intent broadcast cada vez que se pulse sobre el widget o algún elemento del mismo. Para ello, en el método "actualizarWidget()" construiremos un nuevo Intent asociándole una acción personalizada, que en nuestro caso llamaremos "es.ua.jtech.av.ACTION_WIDGET_CLICK". 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. Por último crearemos el PendingIntent mediante el método getBroadcast() y lo asociaremos al evento onClick del control utilizando setOnClickPendingIntent() y pasándole el ID del control. Veamos cómo queda todo esto: // Asociamos los 'eventos' al widget Intent intent = new Intent("es.ua.jtech.av.ACTION_WIDGET_CLICK"); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, widgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT); updateViews.setOnClickPendingIntent(R.id.miwidget, pendingIntent); Es importante que la acción de este intent se añada al Manifest, dentro de la sección "receiver" del Widget, tendremos que declarar otro "intent-filter" para que capture este tipo de eventos y responda a ellos: <intent-filter> <action android:name="es.ua.jtech.av.ACTION_WIDGET_CLICK" /> </intent-filter> Por último tenemos que 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 llamamos a nuestro método estático de actualización pasándole estos datos: @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("es.ua.jtech.av.ACTION_WIDGET_CLICK")) 9 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets { //Obtenemos el ID del widget a actualizar int widgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //Obtenemos el widget manager de nuestro contexto AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); //Actualizamos el widget if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) actualizarWidget(context, widgetManager, widgetId); } Con esto ya hemos finalizado la emisión y captura de eventos al pulsar sobre el Widget (o algún view de este Widget). Dentro del método "actualizarWidget" realizaremos las acciones de actualización, que dependerán del tipo de widget en cuestión que estemos implementando. Para actualizaciones periódicas (con una frecuencia inferior a 30 min) se puede utilizar el AlarmManager que podría ser programado para forzar la actualización del widget, enviando periódicamente un broadcast con la acción que hayamos definido para tal fin. 4.1. Abrir una aplicación Al pulsar sobre un widget, en lugar de lanzar un intent tipo broadcast, también podríamos lanzar un intent que abra un actividad determinada, simplemente realizando: //Comportamiento al pulsar el widget: lanzar alguna actividad Intent defineIntent = new Intent(context, Actividad.class); PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, defineIntent, 0); updateViews.setOnClickPendingIntent(R.id.miwidget, pendingIntent); 5. Servicio de actualización Otra alternativa para refrescar la interfaz gráfica es el uso de un servicio que actualice el widget. Para ello habría que declararlo en el manifest y declarar la clase del servicio UpdateService extends Service dentro de la clase MiWidget. A continuación se muestra un ejemplo de la clase MiWidget que implementa un widget: public class MiWidget extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Inicio de nuestro servicio de actualización: context.startService( new Intent(context, UpdateService.class)); } 10 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets public static class UpdateService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { RemoteViews updateViews = new RemoteViews( getPackageName(), R.layout.miwidget_layout); //Aqui se actualizarían todos los tipos de Views: updateViews.setTextViewText(R.id.TextView01, "Valor con el que refrescamos"); // ... //Y la actualización a través del updateViews creado: ComponentName thisWidget = new ComponentName( this, MiWidget.class); AppWidgetManager.getInstance(this).updateAppWidget( thisWidget, updateViews); return Service.START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } } } 6. Actividad de configuración Algunos widgets muestran una actividad de configuración la primera vez que son añadidos a la pantalla. Cuando el sistema solicita la configuración de un widget se recibe un broadcast intent con la acción android.appwidget.action.APPWIDGET_CONFIGURE, por tanto definiremos en el manifest nuestra actividad con el intent-filter correspondiente, que la ejecutará al recibir la acción: <activity android:name=".MiConfigurationActivity"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity> Esta actividad es una actividad normal, que podemos crear utilizando todos los conceptos que hemos visto, recoger los datos del usuario y almacenarlos de alguna forma (por ejemplo utilizando "SharedPreferences"). Para finalizar la actividad podemos colocar botones para aceptar o cancelar. En el caso de que se cancele tendríamos que realizar lo siguiente: setResult(RESULT_CANCELED); finish(); 11 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets Si se presiona el botón de aceptar la actividad debe devolver un Intent que incluya un extra con el identificador del App Widget, usando la constante EXTRA_APPWIDGET_ID (se puede obtener a partir del Intent que lanza la actividad). A continuación se muestra el código que realizaría esta acción: //Obtenemos el Intent que ha lanzado esta ventana //y recuperamos sus parámetros Intent intentOrigen = getIntent(); Bundle params = intentOrigen.getExtras(); //Obtenemos el ID del widget que se está configurando int widgetId = params.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //Desde el botón aceptar... Intent resultado = new Intent(); resultado.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); setResult(RESULT_OK, resultado); finish(); Esta actividad de configuración también debe ser declarada en el xml que contiene los metadatos del widget: <appwidget-provider ... android:updatePeriodMillis="3600000" android:configure="es.ua.jtech.av.appwidget.MiConfigurationActivity" /> La actividad será mostrada solamente al añadir el widget a la pantalla por primera vez. 12 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved. AppWidgets 13 Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.