Widget Android - Linterna paso a paso (parte2)
Muy buenas a todos otra vez! en esta segunda parte, veremos paso a paso como conseguimos comunicar con nuestro Widget Linterna, os adelanto que es un poco lioso, pero una vez analizado este ejemplo veras como eres capaz de hacer cualquier cosa! ;P ¡¡adelanteeeeeee!!
Como ves en la imagen de la izquierda, en esta segunda parte voy a desgranar paso a paso todos los ficheros que tienen que ver con el widget directamente. Te recuerdo que en la lección de los fundamentos, explique mas o menos los componentes principales.
En la lección anterior vimos la parte "fácil" ;p, que es todo lo que tenia que ver con la Activity que muestra la pantalla blanca al máximo de brillo, en esta lección sin embargo, la cosa se complica un poquito mas...
Yo esto de los widget lo estudie casi al final del curso que hice, y según lo avanzaba (antes de llegar al tema de los widget) pensaba que no debía ser complicado, que ya había pasado lo peor... jejejej ¡pobre de mi! xDD la verdad es que no lo es en absoluto, pero tiene, como todo, "su ciencia" y sus "truquillos" pero tranquilo!, que aquí lo vemos todo como siempre muy despacio y paso a paso! ;p
Antes de continuar, decirte que yo he cometido muchos errores de diseño y funcionamiento en mis primeros widgets, me he peleado mucho con este tema, por eso he dejado esto de los widget para lecciones mas "avanzadas", pero espero que con lo que explique aquí, estos fallos no los cometas tu, espero poder transmitirte mis "pobres" conocimientos en esto, y como digo siempre, si tienes dudas... ¡¡al foro!! ;p
Paso3. Creamos el fichero gráfico del widget Linterna Android
Bueno, este XML es sencillito, nada que no hayamos visto ya en otros ejemplos de este curso, lo único importante, es el concepto de "tamaño del widget".
Como he dicho en alguna ocasión, yo entiendo un widget como una aplicación incrustada en un trocito de una de las pantallas de inicio de nuestro Android, el tamaño de esta "ventana incrustada" esta definida en los metadatos del widget dentro del fichero XML que contiene el componente AppWidgetProviderInfo (que veremos aquí mismo en el paso 4) por lo que aquí, en este fichero de diseño gráfico, hay que asignar como tamaño del widget el máximo disponible, para que se ajuste a esa ventana que definiremos (espero haberme explicado bien) por eso uso la propiedad "fill_parent" en las líneas 3 y 4, ya que el alto y el ancho lo limitamos en el paso 4 (recuerda esto).
<?LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/bg_widget" android:gravity="top" android:orientation="horizontal" android:alpha="0.85" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:scaleType="fitXY" tools:context=".MiWidget_2x1" > <?ImageButton android:id="@+id/btn_pantalla" android:layout_width="0dp" android:padding="0dp" android:layout_height="fill_parent" android:layout_gravity="center" android:layout_weight="1" android:background="@drawable/w" android:contentDescription="btn_Iniciar" android:scaleType="centerInside" android:src="@drawable/pantalla" /> <?ImageView android:layout_width="1dp" android:layout_height="fill_parent" android:background="@android:color/black" android:contentDescription="Line" android:padding="0dp" /> <?ImageButton android:id="@+id/btn_led" android:padding="0dp" android:layout_width="0dp" android:layout_height="fill_parent" android:layout_gravity="center" android:layout_weight="1" android:background="@drawable/w" android:contentDescription="btn_Iniciar" android:scaleType="centerInside" android:src="@drawable/led" /> <?/LinearLayout>
El resultado de todo esto son 2 botones (3 elementos, por que la linea que hay para separar los 2 botones es una imagen) lee las propiedades de cada ImageButton, creo que es bastante "intuitivo" ver cada propiedad, aun así si tienes dudas puedes ir a la lección de los botones! :)
Paso4. Creamos el objeto AppWidgetProviderInfo mediante XML
Bueno, este XML es sencillo, aquí lo que hacemos es asignar las propiedades "generales" del widget, (metadatos) como son: El tamaño que tendrá el widget, relación con el fichero diseño gráfico, miniaturas y otras propiedades. Analicemos paso a paso el codigo! :)
Líneas 1 y 2. Estas líneas son comunes para todos los ficheros de configuración de los widget, como ves iniciamos la 1 con el tipo de fichero de XML y la 2 donde creamos el objeto de tipo <AppWidgetProviderInfo> que es el objeto en si.
Linea3. Asociamos esta configuración al diseño gráfico de tipo XML.
Linea4. Asignamos el nombre que aparecerá en la pantalla de Widget de nuestro Android.
Linea 5 y 6. Asignamos el tamaño del widget (fila y columnas) según tabla de tamaños Android que os vuelvo a adjuntar aquí abajo :).
Linea 7. (No es obligatorio) Definimos si queremos que el widget sea "ajustable" esto es por si queremos que sea mas grande que el tamaño que hayamos pre-definido en las líneas 5 y 6.
Linea 8. (No es obligatorio) Podemos asignarle una imagen en miniatura de previsualizacion que veremos en la pantalla de widget de nuestro android.
Linea 9. Valor en milisegundos para "actualizar" el widget, esto es por si por ejemplo tenemos un widget que muestra "algo" tipo el tiempo que se actualice solo, sin ninguna interacción (no es nuestro caso) ya que la Linterna se actualizara cada vez que toquemos el widget, por lo que asignamos el valor mas alto (creo que 1 hora).
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/miwidget_2x1" android:label="Linterna 2x1" android:minHeight="40dp" android:minWidth="110dp" android:resizeMode="horizontal" android:previewImage="@drawable/widget2x1" android:updatePeriodMillis="3600000" />
Paso5. Creamos la clase Java AppWidgetProvider que gestiona el widget Android
La clase AppWidgetProvider extiende de BroadcastReceiver como una clase de conveniencia para manejar las llamadas a los App Widget. El AppWidgetProvider recibe sólo los eventos que son relevantes para la App Widget, como cuando la App Widget se actualiza, se borra, se activa o se desactiva. Cuando ocurren estos eventos de difusión, el AppWidgetProvider recibe las siguientes llamadas a métodos:
onUpdate ()
(obligatorio) Es el método mas importante, porque se llama cuando se añade cada App Widget a un host. También se llama a actualizar la App Widget a intervalos definidos por el "updatePeriodMillis" que es un atributo en el AppWidgetProviderInfo (que vimos en el paso 4). Este método también es llamado cuando el usuario añade la App Widget por primera vez, por lo que debe realizar una configuración inicial, como definir los controladores de los eventos para las "vistas" o iniciar un servicio que controle este widget si fuese necesario. Como nuestro Widget App acepta eventos de interacción de usuario (los 2 botones de esta linterna) entonces tendremos que implementar los "liseners" para manejar estos eventos del OnClick de los botones.
onAppWidgetOptionsChanged ()
(no obligatorio) Esto se llama cuando el widget se coloca primero y en cualquier momento el widget cambia de tamaño. Esto se usa para modificar o asignar un diseño gráfico distinto ajustándose al nuevo tamaño.
onDeleted(Context, int[])
(obligatorio) Esto se llama cada vez que un Widget App se elimina del anfitrión App Widget.
onEnabled (Context)
(obligatorio) Esto se llama cuando se crea una instancia de la App Widget por primera vez. Por ejemplo, si el usuario agrega dos instancias de su Widget App, esto se llama sólo la primera vez.
onDisabled (Context)
(no obligatorio) Esto se llama cuando se elimina la última instancia de su Widget App desde el host App Widget. Aquí es donde se debe limpiar cualquier trabajo realizado en onEnabled (Contexto) , como eliminar una base de datos temporal...
OnReceive (Context, Intent)
(obligatorio) Esto se ejecuta en cada "Broadcast" y antes de cada uno de los métodos de devolución de llamada anteriores.
El codigo completo quedaría de la siguiente forma (esta comentado)
package es.epinanab.linterna_widget; import es.epinanab.linterna_widget.R.drawable; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.os.Bundle; import android.view.Gravity; import android.widget.RemoteViews; import android.widget.Toast; public class MiWidget_2x1 extends AppWidgetProvider { //Defino las variables de la app static Camera camara; Parameters p; static int estado_led = 0; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; //bucle para actualizar todos los widget (por si se añade mas de uno) for (int i = 0; i < N; i++) { int appWidgetId = appWidgetIds[i]; // Método para actualizar y/o inicializar el widget la primera vez que se ejecuta Actualizar_Widget(context, appWidgetManager, appWidgetId); } } @Override public void onReceive(final Context context, Intent intent) { // TODO Auto-generated method stub super.onReceive(context, intent); // acciones a tomar cuando se recibe el "broadcast" del widget // compruebo la "accion" del boton pulsado que introducimos en el método "actualizar widget" // en la linea 163 if (intent.getAction().equals("iniciar_pantalla")) { // Inicio la activity con la pantalla blanca xxxxxxxxxxxxxxxxxx // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Intent i = new Intent(context, FondoBlanco.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } else if (intent.getAction().equals("iniciar_led")) { // Inicio acciones para encender o apagar el LED xxxxxxxxxxxx // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // Para comprobar si hay LED en el movil PackageManager pm = context.getPackageManager(); // El dispositivo soporta LED en la camara? if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) { Toast t_no_camara = Toast.makeText(context, "No hay LED detectado!!", Toast.LENGTH_LONG); t_no_camara.setGravity(Gravity.TOP, 50, 200); t_no_camara.show(); return; } else { // Si hay LED, enciendo el LED ;P // primero compruebo si estaba encendido mediante esa variable if (estado_led == 1) { // como estaba encendido, lo apagamos y guardo el nuevo estado del LED a OFF estado_led = 0; // El LED estaba ON y lo apago, compruebo primero el objeto camara tiene datos if (camara != null) { camara.stopPreview(); camara.release(); camara = null; } } else { // El LED estaba OFF y lo enciendo camara = Camera.open(); if (camara == null) { Toast.makeText(context, "Error al encender la Camara!", Toast.LENGTH_SHORT).show(); } else { // enciendo el LED p = camara.getParameters(); p.setFlashMode(Parameters.FLASH_MODE_TORCH); camara.setParameters(p); camara.startPreview(); // guardo estado del LED a encendido estado_led = 1; } } } } //Actualizamos todos los widgets (por si hay mas de 1 widget) Bundle extras = intent.getExtras(); if (extras != null) { AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); ComponentName thisAppWidget = new ComponentName( context.getPackageName(), MiWidget_2x1.class.getName()); int[] appWidgetIds = appWidgetManager .getAppWidgetIds(thisAppWidget); for (int appWidgetId : appWidgetIds) { // llamo al método para generar los intents y actualizar iconos Actualizar_Widget(context, appWidgetManager, appWidgetId); } } } @Override public void onEnabled(Context context) { // TODO Auto-generated method stub super.onEnabled(context); } @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO Auto-generated method stub super.onDeleted(context, appWidgetIds); //guardo el estado led a apagado estado_led = 0; //Si elimino widget, apago led (si estubiera encendido) if (camara != null) { camara.stopPreview(); camara.release(); camara = null; } } public static void Actualizar_Widget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { // Localizamos los controles (botones) del widget RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.miwidget_2x1); // Creamos el Intent y lo vinculamos al ImageView PANTALLA Intent i = new Intent(context, MiWidget_2x1.class); i.setAction("iniciar_pantalla"); i.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); PendingIntent pi = PendingIntent.getBroadcast(context, appWidgetId, i, 0); views.setOnClickPendingIntent(R.id.btn_pantalla, pi); // Creamos el Intent y lo vinculamos al ImageView LED Intent i2 = new Intent(context, MiWidget_2x1.class); i2.setAction("iniciar_led"); i2.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); PendingIntent pi2 = PendingIntent.getBroadcast(context, appWidgetId, i2, 0); views.setOnClickPendingIntent(R.id.btn_led, pi2); // Cambiamos la imagen dependiendo de si esta activo o no el LED if (estado_led == 0) { views.setImageViewResource(R.id.btn_led, drawable.led_off); } else if (estado_led == 1) { views.setImageViewResource(R.id.btn_led, drawable.led); } // Enviamos datos al sistema para que actualice el widget appWidgetManager.updateAppWidget(appWidgetId, views); } }
Fijaos bien, que he resaltado unas líneas de codigo, estas líneas llaman a un método que he llamado "Actualizar_widget" y que uso para crear los Intent asociados a cada boton y cambiar el icono del boton que esta relacionado con la linterna para mostrar el estado ON y el estado OFF.
He comentado todo el codigo para explicar paso a paso lo que hace cada linea, si tenéis alguna duda, podéis iniciar un tema en el foro! ;P
Paso6. Añadimos en el AndroidManifest.XML el widget Android
El codigo JAVA asociado a este diseño gráfico seria el siguiente:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="es.epinanab.linterna_widget" android:versionCode="3" android:versionName="1.1" > <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16" /> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera"/> <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" > <activity android:name=".FondoBlanco" android:label="@string/app_name" android:screenOrientation="portrait"> </activity> <receiver android:name="es.epinanab.linterna_widget.MiWidget_2x1" android:label="Linterna 1x2" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_21" /> </receiver> </application> </manifest>
Aqui lo mas importante son las lineas en las que definimos el "receiver" y los "meta-datos" te las señalo para que veas exactamente lo que hay que añadir.
Tambien, si te fijas arriba, he eliminado las lineas que define la app como una aplicacion que se muestra en la lista de aplicaciones, y tambien las lineas donde se definine una Activity inicial, ya que esto es un widget, no hay una activity "inicial" tampoco tiene que mostrarse en la lista de aplicaciones, porque al pulsarla, no debe de pasar nada, por eso lo hay que eliminar estas lineas (te las resalto en azul):
<activity android:name=".FondoBlanco" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
Te recuerdo que tienes el ejemplo ya hecho y listo para descargar en la seccion de ejemplos leccion 10. Recuerda que tienes que descomprimir el fichero e importar ese proyecto a tu Eclipse en el menu Archivo/importar... y seleccionando la carpeta que descomprimiste (tienes un ejemplo de esto aqui)
Si te a gustado este sitio, puedes hacer click en me gusta en Facebook, Google+, Tweeter... es el único precio que te pido por este trabajo! ;P. Compartiendo, ayudaras a otros a encontrar esta web! GRACIASSSS.