Crear un widget de reloj personalizado: Implementación de la configuración de usuario
Spanish (Español) translation by CYC (you can also view the original English article)
El desarrollo de widgets para la plataforma Android implica un ligero y diferente conjunto de tareas que en el desarrollo de aplicaciones estándar. En esta serie de tutoriales, trabajaremos en el proceso de desarrollo de un widget de reloj analógico personalizable. El reloj se basará en la clase Android AnalogClock y se personalizará con tus propios gráficos.
Hasta ahora en esta serie, hemos diseñado e implementado el widget en XML y Java y tenemos un widget de reloj en funcionamiento en donde el usuario puede agregarlo a su pantalla de inicio. En esta parte final de la serie, implementaremos la configuración básica del usuario. En la Parte 2, creamos varios diseños de reloj, por lo que ahora le permitiremos al usuario elegir entre ellos.
Esta es la Parte 4 de 4 en una serie sobre Cómo construir un widget de reloj analógico Android personalizable en cuatro tutoriales:
- Configuración del proyecto Android de un Widget
- Diseñando el reloj
- Recibir actualizaciones y lanzar
- Implementando la configuración del usuario
La configuración del usuario en nuestro widget implicará una nueva clase Activity de Java, que presentará la selección de opciones al usuario. Cuando el usuario selecciona un diseño, actualizaremos la apariencia del widget y almacenaremos la opción del usuario en las preferencias compartidas de la aplicación. También extenderemos la clase widget para manejar los clics del usuario en el widget y para leer de las preferencias compartidas para la elección del usuario. Además de trabajar con estos dos archivos Java, crearemos un nuevo archivo de valores y un archivo de diseño XML para la actividad seleccionada junto con algunas imágenes para mostrar dentro de ella.
Paso 1: Manejar clics de widgets
Primero, agreguemos algún código a la clase widget para detectar los clics del usuario. En la clase "ClockWidget", dentro de la instrucción "if" en el método "onReceive", después de la línea en la que recuperamos el objeto Remote Views, agrega el siguiente código para crear un Intent parael selector Activity que vamos a usar:
1 |
|
2 |
Intent choiceIntent = new Intent(context, ClockChoice.class); |
No te preocupes por los errores de Eclipse por ahora, desaparecerán cuando creamos la nueva clase Activity en el siguiente paso. Después de esta línea, crea un Pending Intent de la siguiente manera:
1 |
|
2 |
PendingIntent clickPendIntent = PendingIntent.getActivity |
3 |
(context, 0, choiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); |
Lanzar Actividades en clics de widgets es un poco diferente, como puedes ver. Ten en cuenta que pasamos el objeto Context y una referencia al nuevo Intent. Ahora agrega el siguiente código que especifica que Pending Intent se debe iniciar cuando se hace clic en el widget:
1 |
|
2 |
views.setOnClickPendingIntent(R.id.custom_clock_widget, clickPendIntent); |
Especificamos el widget haciendo referencia a la ID del diseño principal en el archivo XML "clock_widget_layout". Necesitamos usar las vistas remotas para referirnos a los elementos de la interfaz de usuario ya que estamos en una clase Widget en lugar de una clase Activity. Añadiremos más código a esta clase más tarde.
Paso 2: Crea un Selector Activity
Ahora, para la clase Activity en la que permitimos que los usuarios elijan un diseño. Crea una nueva clase en tu proyecto haciendo clic con el botón derecho o seleccionando la carpeta del paquete de origen y seleccionando "Archivo" - luego selecciona "Nuevo", "Clase" e ingresa "ClockChoice" como nombre de clase. Eclipse abrirá la nueva clase cuando hagas clic en Finalizar. Recuerda que incluimos esta Activity en el archivo de Manifiesto del proyecto en la Parte 1.
Haz de tu nueva clase una Activity y otra que maneje los clics de los usuarios al extender su línea de apertura de la siguiente manera:
1 |
|
2 |
public class ClockChoice extends Activity implements OnClickListener { |
Nuevamente, simplemente ignora cualquier mensaje de error, aparecerán hasta que proporcionemos el método "onClick". Necesitarás las siguientes declaraciones de importación:
1 |
|
2 |
import android.app.Activity; |
3 |
import android.view.View.OnClickListener; |
Proporcione el método Activity "onCreate" dentro de la clase de la siguiente manera:
1 |
|
2 |
public void onCreate(Bundle savedInstanceState) { |
3 |
super.onCreate(savedInstanceState); |
4 |
setContentView(R.layout.clock_choice); |
5 |
|
6 |
}
|
Crearemos el archivo de diseño en el siguiente paso. Necesitarás otra importación:
1 |
|
2 |
import android.os.Bundle; |
Vamos a agregar más código a esta clase más tarde.
Paso 3: Diseño del Selector Activity
Vamos a crear el diseño que especificamos en la clase Activity anterior. Crea un nuevo archivo de diseño haciendo clic con el botón derecho o seleccionando la carpeta "res/layout" y seleccionando "Archivo", luego haciendo clic en "Nuevo", "Archivo" e ingresando "clock_choice.xml" para que coincida con la referencia en el código anterior.



Cuando Eclipse abra el archivo, selecciona la pestaña "clock_choice.xml" para editar el código. Ingresa el siguiente esquema de diseño en tu archivo:
1 |
|
2 |
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android" |
3 |
android:layout_height="wrap_content" |
4 |
android:layout_width="fill_parent"> |
5 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
6 |
android:orientation="vertical" |
7 |
android:layout_width="wrap_content" |
8 |
android:layout_height="wrap_content" |
9 |
android:padding="10dp"> |
10 |
|
11 |
</LinearLayout>
|
12 |
</ScrollView>
|
El diseño incluye una plantilla lineal dentro de una vista de desplazamiento. Dentro del diseño lineal, primero agrega un texto informativo de la siguiente manera:
1 |
|
2 |
<TextView
|
3 |
android:layout_width="wrap_content" |
4 |
android:layout_height="wrap_content" |
5 |
android:text="@string/choice_intro" |
6 |
android:textStyle="italic" /> |
Agregue el recurso String indicado aquí a tu archivo "strings.xml", asegurándote de incluirlo en ambas copias del archivo de strings, en "valores" y "valores-v14":
1 |
|
2 |
<string name="choice_intro">Choose an option:</string> |
Ahora vamos a mostrar una imagen para representar el diseño de cada reloj. Estas imágenes van a actuar como botones para que los usuarios presionen al seleccionar un diseño. Crea tus imágenes ahora, recordando incluir diferentes versiones para cada densidad. Si no quieres crear el tuyo por el momento, puedes usar las imágenes en el enlace de descarga al final del tutorial. Ahí están las versiones de densidad media de cada uno:



Guarda cada versión de tus imágenes en las carpetas drawable para tu aplicación, recordando usar los mismos nombres en cada carpeta de densidad.
Ahora regresa a tu archivo XML de plantilla "clock_choice". Agrega un botón de imagen para cada diseño que estés utilizando, con lo siguiente para los tres diseños de ejemplo que hemos utilizado, después de la vista de texto:
1 |
|
2 |
<ImageButton
|
3 |
android:id="@+id/design_0" |
4 |
android:layout_width="wrap_content" |
5 |
android:layout_height="wrap_content" |
6 |
android:src="@drawable/clock_image" |
7 |
android:contentDescription="@string/design1" |
8 |
android:background="#00000000" |
9 |
android:padding="5dp"/> |
10 |
<ImageButton
|
11 |
android:id="@+id/design_1" |
12 |
android:layout_width="wrap_content" |
13 |
android:layout_height="wrap_content" |
14 |
android:src="@drawable/clock_image_stone" |
15 |
android:contentDescription="@string/design2" |
16 |
android:background="#00000000" |
17 |
android:padding="5dp"/> |
18 |
<ImageButton
|
19 |
android:id="@+id/design_2" |
20 |
android:layout_width="wrap_content" |
21 |
android:layout_height="wrap_content" |
22 |
android:src="@drawable/clock_image_metal" |
23 |
android:contentDescription="@string/design3" |
24 |
android:background="#00000000" |
25 |
android:padding="5dp" /> |
Hay varios puntos para notar aquí. Primero, observa que cada Botón de imagen tiene una ID con la misma sintaxis pero un entero creciente al final; lo usaremos para recorrer los diseños en Java, por lo que si incluyes más de tres diseños, asegúrate de asignarlos iterando números, como "design_3", etc. Además de los atributos de diseño, cada elemento también indica el recurso drawable relevante, por lo tanto, modifícalos si los archivos de imagen que acabas de crear tienen diferentes nombres. Finalmente, los atributos de descripción de contenido se refieren a recursos String, así que agrégalos a los archivos "strings.xml" en tus carpetas "valores" y "valores-v14" de la siguiente manera:
1 |
|
2 |
<string name="design1">Default design</string> |
3 |
<string name="design2">Stone design</string> |
4 |
<string name="design3">Metal design</string> |
Modifica estas descripciones si es necesario para adaptarte a tus propios diseños.
Hagamos uso de los temas de Android para automatizar ciertos aspectos de la apariencia del selector Activity. Abre tu archivo Manifiesto de proyecto y extiende el elemento que representa el selector Activity de reloj de la siguiente manera:
1 |
|
2 |
<activity android:name=".ClockChoice" android:theme="@android:style/Theme.Dialog"> |
3 |
</activity>
|
Agregar el tema del diálogo hace que Activity aparezca superpuesta en la pantalla de inicio: Esta es su apariencia con los diseños de muestra que hemos utilizado en esta serie de tutoriales:



Esto es lo que aparecerá cuando el usuario haga clic en el widget. Si la pantalla del dispositivo es demasiado pequeña para acomodar los tres diseños, se desplazará a medida que el diseño utiliza una vista de desplazamiento. Cuando el usuario selecciona un diseño, la apariencia del widget se actualizará y esta actividad de elección desaparecerá.
No hace falta decir que es posible que desees modificar el número de posibles diseños dentro de tu aplicación de widget de reloj. Para facilitar este proceso, vamos a hacer que nuestro código Java lea la cantidad de diseños dinámicamente. Para este propósito, vamos a usar un valor numérico para realizar un seguimiento de la cantidad de diseños que estamos usando. Crea un nuevo archivo en cada una de tus dos carpetas de valores, haz clic derecho o selecciona cada carpeta alternadamente y elige "Archivo", luego "Nuevo", "Archivo" e ingresa "numbers.xml" como el nombre del archivo.



Selecciona la pestaña "numbers.xml" e ingresa el siguiente código en tu nuevo archivo:
1 |
|
2 |
<resources>
|
3 |
<integer name="num_clocks">3</integer> |
4 |
</resources>
|
Modifica el número si utilizaste una cantidad diferente de diseños, asegurándote de que el valor coincida con la cantidad de Botones de imagen que incluiste en el diseño y la cantidad de elementos del Reloj análogo que tienes en el diseño de tu widget. Recuerda que necesitas una copia del archivo "numbers.xml" en ambas carpetas de valores, así que copia esto y pégalo si es necesario.
Si alteras la cantidad de diseños que estás utilizando en algún punto, debes modificar el valor en el archivo "numbers.xml", agregar cada diseño como un elemento Clock analógico en el archivo "clock_widget_layout" de la aplicación y Botón de imagen para cada diseño en el archivo de diseño de actividad del selector.
Paso 4: Manejar la elección del usuario
Manejemos la interacción del usuario con la selección de diseños de reloj. Abre tu archivo Activity "ClockChoice". Dentro de la clase, antes del método "onCreate", agrega las siguientes variables de instancia:
1 |
|
2 |
//count of designs
|
3 |
private int numDesigns; |
4 |
//image buttons for each design
|
5 |
private ImageButton[] designBtns; |
6 |
//identifiers for each clock element
|
7 |
private int[] designs; |
Utilizaremos estas variables para iterar a través de varios diseños. Agrega otra importación:
1 |
|
2 |
import android.widget.ImageButton; |
Dentro del método "onCreate", después del código existente, crea una instancia de la variable para realizar un seguimiento del número de diseños de reloj, de la siguiente manera:
1 |
|
2 |
numDesigns = this.getResources().getInteger(R.integer.num_clocks); |
Aquí recuperamos el valor en el archivo de números XML que creamos antes; podremos usar este valor como referencia al iterar a través de los diseños. A continuación, ejemplifica las matrices para los botones de diseño y los elementos del reloj:
1 |
|
2 |
designBtns = new ImageButton[numDesigns]; |
3 |
designs = new int[numDesigns]; |
La primera matriz se referirá a los botones de imagen que estamos usando dentro del selector activity para detectar la elección del usuario. La segunda matriz va a almacenar referencias a los elementos del reloj analógico para cada diseño en el archivo de diseño del widget en sí.
Ahora agrega un ciclo para iterar a través de los diseños:
1 |
|
2 |
for(int d=0; d<numDesigns; d++){ |
3 |
|
4 |
}
|
Dentro del bucle, recupera una referencia a cada elemento del reloj analógico en el diseño del widget:
1 |
|
2 |
designs[d] = this.getResources().getIdentifier |
3 |
("AnalogClock"+d, "id", getPackageName()); |
Si miras hacia atrás en el archivo "clock_widget_layout", verás que cada elemento del Reloj Analógico tiene una ID que comprende "AnalogClock" seguido de un número entero en incremento; lo usamos aquí para recuperar una referencia a cada uno usando el contador de bucle. Luego, aún dentro del ciclo, recupera los valores de ID para los botones de imagen en el diseño del selector activity:
1 |
|
2 |
designBtns[d]=(ImageButton)findViewById(this.getResources().getIdentifier |
3 |
("design_"+d, "id", getPackageName())); |
Aquí estamos usando "findViewById" para obtener una referencia al elemento del Botón de imagen relevante, pasando el ID, que comprende "design_" seguido del entero que se incrementa. Ahora configura el receptor del clic para cada uno de estos botones y así podremos manejar los clics en esta clase Activity. Mientras aún estás dentro del ciclo:
1 |
|
2 |
designBtns[d].setOnClickListener(this); |
Ahora podemos continuar con el método clic para los botones de diseño. Después del método "onCreate" en la clase "ClockChoice", agrega un método "onClick" de la siguiente manera:
1 |
|
2 |
public void onClick(View v) { |
3 |
|
4 |
}
|
Agrega la siguiente importación:
1 |
|
2 |
import android.view.View; |
En el método "onClick", primero debemos determinar qué botón ha presionado el usuario. Agrega el siguiente ciclo dentro del método para este propósito:
1 |
|
2 |
int picked = -1; |
3 |
for(int c=0; c<numDesigns; c++){ |
4 |
if(v.getId()==designBtns[c].getId()){ |
5 |
picked=c; |
6 |
break; |
7 |
}
|
8 |
}
|
Aquí verificamos el ID de la Vista a la cual se le ha hecho clic en comparación con los ID que hemos almacenado en la matriz del Botón de Imagen. Cuando el ciclo sale, tenemos el índice elegido almacenado en una variable local. Después de que el bucle almacena una referencia a la ID para el elemento de reloj analógico elegido:
1 |
|
2 |
int pickedClock = designs[picked]; |
Ahora necesitamos obtener una referencia a los elementos de diseño del widget, para lo cual necesitamos las Vistas remotas, pasando el diseño del widget como referencia:
1 |
|
2 |
RemoteViews remoteViews = new RemoteViews |
3 |
(this.getApplicationContext().getPackageName(), |
4 |
R.layout.clock_widget_layout); |
Para esto necesitas otra declaración de importación:
1 |
|
2 |
import android.widget.RemoteViews; |
Recuerda que incluimos cada diseño de reloj análogo en el archivo de diseño del widget, esto es porque no podemos alterar dinámicamente los atributos de un reloj análogo, como los drawables para el dial y las manecillas, por lo tanto, incluimos todos los diseños y establecemos todos pero solamente uno para ser invisible. Antes de configurar el diseño del reloj elegido para que sea visible, estableceremos los otros para que sean invisibles, lo que podemos hacer en un ciclo de la siguiente manera:
1 |
|
2 |
for(int d=0; d<designs.length; d++){ |
3 |
if(d!=pickedClock) |
4 |
remoteViews.setViewVisibility(designs[d], View.INVISIBLE); |
5 |
}
|
Usamos la matriz en la que almacenamos los valores de ID del elemento del Reloj Análogo para configurar cada uno como invisible, siempre que no sea el elegido. Ahora podemos configurar el diseño elegido para que sea visible, después de este ciclo:
1 |
|
2 |
remoteViews.setViewVisibility(pickedClock, View.VISIBLE); |
Ahora, como se trata de una aplicación de widgets, debemos actualizar la apariencia del widget de la siguiente manera:
1 |
|
2 |
//get component name for widget class
|
3 |
ComponentName comp = new ComponentName(this, ClockWidget.class); |
4 |
//get AppWidgetManager
|
5 |
AppWidgetManager appWidgetManager = |
6 |
AppWidgetManager.getInstance(this.getApplicationContext()); |
7 |
//update
|
8 |
appWidgetManager.updateAppWidget(comp, remoteViews); |
Esto es similar a la forma en que actualizamos el widget en la clase de proveedor de widgets, pero con un par de pasos de procesamiento adicionales porque estamos en una clase de Activity aquí. Deberás agregar estas importaciones:
1 |
|
2 |
import android.appwidget.AppWidgetManager; |
3 |
import android.content.ComponentName; |
Ahora la apariencia del widget se actualizará de acuerdo con la elección del usuario.
Paso 5: Actualizar las preferencias compartidas
A continuación, vamos a usar la aplicación Preferencias compartidas para almacenar la opción de diseño del usuario. Haz una copia de seguridad en la parte superior de la clase Activity de elección de reloj, agrega otra variable de instancia:
1 |
|
2 |
private SharedPreferences clockPrefs; |
Ahora, al final del método "onCreate", después del código existente, agrega lo siguiente para crear una instancia de la variable Shared Preferences:
1 |
|
2 |
clockPrefs = getSharedPreferences("CustomClockPrefs", 0); |
Utilizaremos el mismo nombre de preferencias cada vez que accedamos a los datos con respecto a la elección del usuario. Ahora baja hasta el final del método "onClick", después del código en el que actualizamos el widget. Obtén el editor de preferencias de la siguiente manera:
1 |
|
2 |
SharedPreferences.Editor custClockEdit = clockPrefs.edit(); |
Ahora pasa los datos sobre la elección del usuario al editor y confírmalos:
1 |
|
2 |
custClockEdit.putInt("clockdesign", picked); |
3 |
custClockEdit.commit(); |
Especificamos un nombre para el valor de los datos y el ID del diseño elegido. Finalmente, aún dentro de "onClick", agrega lo siguiente a medida que el trabajo de Activity finalice:
1 |
|
2 |
finish(); |
Paso 6: Verificar las preferencias compartidas
Ahora podemos hacer uso de los datos de preferencias compartidas desde dentro de la clase widget. Abre tu clase "ClockWidget", que extiende AppWidgetProvider. Los algoritmos que usamos aquí serán similares a los que usamos anteriormente para administrar los diseños de reloj. Agrega las siguientes variables de instancia adicionales en la parte superior:
1 |
|
2 |
//preferences
|
3 |
private SharedPreferences custClockPrefs; |
4 |
//number of possible designs
|
5 |
private int numClocks; |
6 |
//IDs of Analog Clock elements
|
7 |
int[] clockDesigns; |
Necesitarás la siguiente importación:
1 |
|
2 |
import android.content.SharedPreferences; |
En el método "onReceive", antes del código existente, recupera la cantidad de diseños de nuestro recurso de valor:
1 |
|
2 |
numClocks = context.getResources().getInteger(R.integer.num_clocks); |
A continuación, crea una instancia de la matriz ID del reloj de la siguiente manera:
1 |
|
2 |
clockDesigns = new int[numClocks]; |
Ahora recorre esta matriz, estableciendo cada elemento como el ID para el elemento Reloj análogo relevante en el diseño del widget:
1 |
|
2 |
for(int d=0; d<numClocks; d++){ |
3 |
clockDesigns[d]=context.getResources().getIdentifier |
4 |
("AnalogClock"+d, "id", context.getPackageName()); |
5 |
}
|
Ahora ve al contenido de la declaración "if" en el método "onReceive", después de la línea en la que recuperamos las Vistas Remotas y antes de la línea en la que creamos el Intento para la clase de elección de reloj, obtén las Preferencias Compartidas y verifica para el valor de los datos que establecemos para la elección del usuario:
1 |
|
2 |
custClockPrefs = context.getSharedPreferences("CustomClockPrefs", 0); |
3 |
int chosenDesign = custClockPrefs.getInt("clockdesign", -1); |
El nombre de las Preferencias compartidas y el nombre del valor de los datos debe coincidir con lo que incluiste al establecer la elección del usuario en el Selector Activity anterior. El usuario puede no haber elegido un diseño todavía, así que incluye una declaración "if" para verificar:
1 |
|
2 |
if(chosenDesign>=0){ |
3 |
|
4 |
}
|
Dentro de la instrucción "if", recorre primero los diseños, estableciendo cada elemento invisible si no es el elegido:
1 |
|
2 |
for(int d=0; d<numClocks; d++){ |
3 |
if(d!=chosenDesign) |
4 |
views.setViewVisibility(clockDesigns[d], View.INVISIBLE); |
5 |
}
|
Necesitarás otra importación:
1 |
|
2 |
import android.view.View; |
Ahora establece el diseño elegido en visible:
1 |
|
2 |
views.setViewVisibility(clockDesigns[chosenDesign], View.VISIBLE); |
Ahora, cuando se actualiza el widget, si el usuario ha elegido un diseño, se mostrará ese diseño.
Conclusión
¡Ese es el widget de reloj terminado! Puedes ejecutarlo en un emulador o en un dispositivo para probarlo. Debería ejecutarse continuamente, actualizando la hora y reflejando la elección de diseño del usuario.



Tómate tu tiempo para revisar los diversos elementos de la aplicación y asegúrate de comprender cómo funcionan conjuntamente. El proceso de desarrollo de cualquier otra aplicación de widgets implicará muchos de los mismos pasos. Los elementos vitales de una aplicación de widgets son: el elemento XML "appwidget-provider", el archivo Manifest, la clase que extiende AppWidgetProvider y, por supuesto, los elementos visuales, incluidos los diseños y los recursos.
Si buscas desarrollar aún más las habilidades que has aprendido en esta serie, hay una variedad de opciones. Si deseas proporcionar una configuración de usuario avanzada para tus widgets, puedez usar Preference Activity junto con la acción APPWIDGET_CONFIGURE. Si deseas incluir pantallas de reloj digital junto con tus opciones analógicas, el proceso es un poco más complejo. Puedes incluir la clase predeterminada Digital Clock en una aplicación estándar, pero no en una aplicación de widgets, por lo que debes implementar la visualización de la hora digital y las actualizaciones tú mismo, utilizando componentes adicionales tales como servicios y administradores de alarmas.
Siéntete libre de usar el código fuente completo y las imágenes para este tutorial proporcionado en el enlace de descarga.



