Cómo crear un widget simple

Los widgets son vistas de apps en miniatura que puedes incorporar en otras apps (como la pantalla principal) y recibir actualizaciones periódicas. Estas vistas se denominan widgets en la interfaz de usuario, y puedes publicar una con un proveedor de widgets de apps (o proveedor de widgets). Un componente de la app que contiene otros widgets se denomina host de widget de la app (o host de widget). En la Figura 1, se muestra un widget de música de ejemplo:

Ejemplo de widget de música
Figura 1: Ejemplo de un widget de música.

En este documento, se describe cómo publicar un widget usando un proveedor de widgets. Para obtener detalles sobre cómo crear tu propio AppWidgetHost para alojar widgets de apps, consulta Cómo compilar un host de widgets.

Para obtener información sobre cómo diseñar tu widget, consulta Descripción general de los widgets de apps.

Componentes del widget

Para crear un widget, necesitas los siguientes componentes básicos:

Objeto AppWidgetProviderInfo
Describe los metadatos de un widget, como el diseño, la frecuencia de actualización y la clase AppWidgetProvider. AppWidgetProviderInfo se define en XML, como se describe en este documento.
Clase AppWidgetProvider
Define los métodos básicos que te permiten interactuar de manera programática con el widget. De esta manera, recibes transmisiones cuando se actualiza, se habilita, se inhabilita o se borra el widget. Declaras AppWidgetProvider en el manifiesto y, luego, la implementas, como se describe en este documento.
Diseño de la vista
Define el diseño inicial del widget. El diseño se define en XML, como se describe en este documento.

En la Figura 2, se muestra cómo estos componentes se ajustan al flujo general de procesamiento de widgets de la app.

Flujo de procesamiento del widget de la app
Figura 2: Flujo de procesamiento del widget de la app.

Si tu widget necesita configuración del usuario, implementa la actividad de configuración del widget de la app. Esta actividad permite a los usuarios modificar la configuración del widget, por ejemplo, la zona horaria de un widget de reloj.

También recomendamos las siguientes mejoras: diseños flexibles de widgets, mejoras varias, widgets avanzados, widgets de colecciones y creación de un host de widgets.

Declara el XML de AppWidgetProviderInfo

El objeto AppWidgetProviderInfo define las cualidades esenciales de un widget. Define el objeto AppWidgetProviderInfo en un archivo de recursos XML con un solo elemento <appwidget-provider> y guárdalo en la carpeta res/xml/ del proyecto.

Esto se muestra en el siguiente ejemplo:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/example_loading_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Atributos de tamaño del widget

En la pantalla principal predeterminada, se colocan los widgets en la ventana según una cuadrícula de celdas que tienen una altura y un ancho definidos. La mayoría de las pantallas principales solo permiten que los widgets tengan tamaños que sean múltiplos de números enteros de las celdas de la cuadrícula; por ejemplo, dos celdas horizontalmente por tres celdas verticalmente.

Los atributos de tamaño del widget te permiten especificar un tamaño predeterminado para tu widget y proporcionar límites inferior y superior para el tamaño del widget. En este contexto, el tamaño predeterminado de un widget es el que adopta cuando se agrega a la pantalla principal por primera vez.

En la siguiente tabla, se describen los atributos <appwidget-provider> relacionados con el tamaño del widget:

Atributos y descripción
targetCellWidth y targetCellHeight (Android 12), minWidth y minHeight
  • A partir de Android 12, los atributos targetCellWidth y targetCellHeight especifican el tamaño predeterminado del widget en términos de celdas de cuadrícula. Estos atributos se ignoran en Android 11 y versiones anteriores, y se pueden ignorar si la pantalla principal no admite un diseño basado en cuadrículas.
  • Los atributos minWidth y minHeight especifican el tamaño predeterminado del widget en dp. Si los valores de altura o ancho mínimos de un widget no coinciden con las dimensiones de las celdas, los valores se redondean hacia arriba según el tamaño de la celda más cercana.
Recomendamos especificar ambos conjuntos de atributos (targetCellWidth y targetCellHeight, y minWidth y minHeight) para que tu app pueda volver a usar minWidth y minHeight si el dispositivo del usuario no admite targetCellWidth ni targetCellHeight. Si se admiten, los atributos targetCellWidth y targetCellHeight tienen prioridad sobre los atributos minWidth y minHeight.
minResizeWidth y minResizeHeight Especifica el tamaño mínimo absoluto del widget. Estos valores especifican el tamaño por debajo del cual el widget es ilegible o inutilizable. El uso de estos atributos permite al usuario cambiar el tamaño del widget a uno menor que el predeterminado. El atributo minResizeWidth se ignora si es mayor que minWidth o si el cambio de tamaño horizontal no está habilitado. Consulta resizeMode. Del mismo modo, el atributo minResizeHeight se ignora si es mayor que minHeight o si el cambio de tamaño vertical no está habilitado.
maxResizeWidth y maxResizeHeight Especifica el tamaño máximo recomendado del widget. Si los valores no son un múltiplo de las dimensiones de la celda de la cuadrícula, se redondean hacia arriba al tamaño de celda más cercano. El atributo maxResizeWidth se ignora si es menor que minWidth o si el cambio de tamaño horizontal no está habilitado. Consulta resizeMode. Del mismo modo, el atributo maxResizeHeight se ignora si es mayor que minHeight o si no está habilitado el cambio de tamaño vertical. Se introdujo en Android 12.
resizeMode Especifica las reglas por las que se puede cambiar el tamaño de un widget. Puedes usar este atributo para hacer que los widgets de la pantalla principal puedan cambiar de tamaño en sentido horizontal, vertical o en ambos ejes. Los usuarios deben mantener presionado un widget para mostrar los controladores de cambio de tamaño y, luego, arrastrar los controladores de forma horizontal o vertical para cambiar el tamaño en la cuadrícula de diseño. Los valores del atributo resizeMode incluyen horizontal, vertical y none. Para declarar un widget que puede cambiar de tamaño horizontal y verticalmente, usa horizontal|vertical.

Ejemplo

Para ilustrar cómo los atributos de la tabla anterior afectan el tamaño del widget, asumamos las siguientes especificaciones:

  • Una celda de cuadrícula tiene 30 dp de ancho y 50 dp de alto.
  • Se proporciona la siguiente especificación de atributos:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

A partir de Android 12:

Usa los atributos targetCellWidth y targetCellHeight como el tamaño predeterminado del widget.

De forma predeterminada, el tamaño del widget es 2 x 2. Se puede cambiar el tamaño del widget a 2 × 1 o hasta 4 × 3.

Android 11 y versiones anteriores:

Usa los atributos minWidth y minHeight para calcular el tamaño predeterminado del widget.

El ancho predeterminado = Math.ceil(80 / 30) = 3

La altura predeterminada = Math.ceil(80 / 50) = 2

El tamaño del widget es de 3 × 2 de forma predeterminada. El tamaño del widget se puede reducir a 2 × 1 o hasta pantalla completa.

Atributos adicionales del widget

En la siguiente tabla, se describen los atributos <appwidget-provider> correspondientes a otras cualidades distintas del tamaño del widget.

Atributos y descripción
updatePeriodMillis Define la frecuencia con la que el framework de widgets solicita una actualización desde el AppWidgetProvider llamando al método de devolución de llamada onUpdate(). Con este valor, no se garantiza que la actualización real ocurra exactamente a tiempo, y recomendamos realizarla con la menor frecuencia posible (no más de una vez por hora) para conservar la batería. Si deseas obtener la lista completa de consideraciones para elegir un período de actualización adecuado, consulta Optimizaciones para actualizar el contenido de los widgets.
initialLayout Apunta al recurso de diseño que define el diseño del widget.
configure Define la actividad que se inicia cuando el usuario agrega el widget, lo que le permite configurar sus propiedades. Consulta Cómo permitir que los usuarios configuren widgets. A partir de Android 12, tu app puede omitir la configuración inicial. Consulta Cómo usar la configuración predeterminada del widget para obtener más información.
description Especifica la descripción que el selector de widgets mostrará para tu widget. Se introdujo en Android 12.
previewLayout (Android 12) y previewImage (Android 11 y versiones anteriores)
  • A partir de Android 12, el atributo previewLayout especifica una vista previa escalable, que proporcionas como un diseño XML configurado con el tamaño predeterminado del widget. Lo ideal es que el XML de diseño especificado como este atributo sea el mismo que el del widget real con valores predeterminados realistas.
  • En Android 11 o versiones anteriores, el atributo previewImage especifica una vista previa del aspecto del widget después de la configuración, es decir, lo que el usuario ve cuando selecciona el widget. Si no se proporciona, el usuario ve el ícono del selector de tu app. Este campo corresponde al atributo android:previewImage del elemento <receiver> del archivo AndroidManifest.xml.
Nota: Te recomendamos que especifiques los atributos previewImage y previewLayout para que tu app pueda usar previewImage como alternativa si el dispositivo del usuario no admite previewLayout. Para obtener más información, consulta Retrocompatibilidad con vistas previas escalables de widgets.
autoAdvanceViewId Especifica el ID de vista de la subvista del widget que el host del widget avanza automáticamente.
widgetCategory Declara si el widget se puede mostrar en la pantalla principal (home_screen), la pantalla de bloqueo (keyguard) o ambas. Para Android 5.0 y versiones posteriores, solo es válido home_screen.
widgetFeatures Declara las funciones compatibles con el widget. Por ejemplo, si quieres que tu widget use su configuración predeterminada cuando un usuario lo agrega, especifica las marcas configuration_optional y reconfigurable. De esta manera, se evita iniciar la actividad de configuración después de que un usuario agrega el widget. El usuario aún puede reconfigurar el widget más adelante.

Usa la clase AppWidgetProvider para controlar las transmisiones de widgets

La clase AppWidgetProvider controla las transmisiones de widgets y los actualiza en respuesta a eventos del ciclo de vida de los widgets. En las siguientes secciones, se describe cómo declarar AppWidgetProvider en el manifiesto y, luego, implementarlo.

Cómo declarar un widget en el manifiesto

Primero, declara la clase AppWidgetProvider en el archivo AndroidManifest.xml de la app, como se muestra en el siguiente ejemplo:

<receiver android:name="ExampleAppWidgetProvider"
                 android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>

El elemento <receiver> requiere el atributo android:name, que especifica el AppWidgetProvider que usa el widget. El componente no se debe exportar a menos que un proceso independiente deba transmitir a tu AppWidgetProvider, lo que, por lo general, no es el caso.

El elemento <intent-filter> debe incluir un elemento <action> con el atributo android:name. Este atributo especifica que el AppWidgetProvider acepta la transmisión de ACTION_APPWIDGET_UPDATE. Esta es la única emisión que debes declarar de manera explícita. AppWidgetManager envía automáticamente todas las demás transmisiones de widgets a AppWidgetProvider según sea necesario.

El elemento <meta-data> especifica el recurso AppWidgetProviderInfo y requiere los siguientes atributos:

  • android:name: Especifica el nombre de los metadatos. Usa android.appwidget.provider para identificar los datos como el descriptor AppWidgetProviderInfo.
  • android:resource: Especifica la ubicación del recurso AppWidgetProviderInfo.

Implementa la clase AppWidgetProvider

La clase AppWidgetProvider extiende BroadcastReceiver como una clase de conveniencia para controlar las transmisiones de widgets. Solo recibe las transmisiones de eventos que son relevantes para el widget, como cuando el widget se actualiza, se borra, se habilita o se inhabilita. Cuando se producen estos eventos de emisión, se llaman a los siguientes métodos AppWidgetProvider:

onUpdate()
Se llama a este método para actualizar el widget en intervalos definidos por el atributo updatePeriodMillis en el AppWidgetProviderInfo. Consulta la tabla que describe los atributos adicionales de los widgets en esta página para obtener más información.
También se llama a este método cuando el usuario agrega el widget, por lo que realiza la configuración esencial, como definir controladores de eventos para objetos View o iniciar trabajos para cargar datos que se mostrarán en el widget. Sin embargo, si declaras una actividad de configuración sin la marca configuration_optional, no se llamará a este método cuando el usuario agregue el widget, pero se llamará para las actualizaciones posteriores. Es responsabilidad de la actividad de configuración realizar la primera actualización cuando se completa la configuración. Consulta Cómo permitir que los usuarios configuren widgets de apps para obtener más información.
La devolución de llamada más importante es onUpdate(). Consulta Cómo controlar eventos con la clase onUpdate() en esta página para obtener más información.
onAppWidgetOptionsChanged()

Se llama a este método cuando se coloca por primera vez el widget y cada vez que se le cambia el tamaño. Usa esta devolución de llamada para mostrar u ocultar contenido en función de los rangos de tamaño del widget. Para obtener los rangos de tamaño y, a partir de Android 12, la lista de tamaños posibles que puede tener una instancia de widget, llama a getAppWidgetOptions(), que muestra un Bundle que incluye lo siguiente:

onDeleted(Context, int[])

Se llama a este método cada vez que se borra un widget del host de widgets.

onEnabled(Context)

Se llama a este método cuando se crea una instancia del widget por primera vez. Por ejemplo, si el usuario agrega dos instancias de tu widget, solo se lo llama la primera vez. Si necesitas abrir una nueva base de datos o realizar otra configuración única para todas las instancias del widget, este es un buen lugar para hacerlo.

onDisabled(Context)

Se llama a este método cuando se borra la última instancia de tu widget del host de widgets. Aquí debes limpiar los trabajos realizados en onEnabled(Context), como borrar una base de datos temporal.

onReceive(Context, Intent)

Se llama a este método para cada emisión y antes de cada uno de los métodos de devolución de llamada anteriores. Por lo general, no es necesario implementar este método, ya que la implementación predeterminada de AppWidgetProvider filtra todas las transmisiones de widgets y llama a los métodos anteriores según corresponda.

Debes declarar tu implementación de la clase AppWidgetProvider como un receptor de emisión con el elemento <receiver> en AndroidManifest. Consulta Cómo declarar un widget en el manifiesto en esta página para obtener más información.

Cómo controlar eventos con la clase onUpdate()

La devolución de llamada de AppWidgetProvider más importante es onUpdate(), ya que se la llama cuando se agrega cada widget a un host, a menos que uses una actividad de configuración sin la marca configuration_optional. Si tu widget acepta eventos de interacción con el usuario, registra los controladores de eventos en esta devolución de llamada. Si tu widget no crea archivos temporales o bases de datos, ni realiza otro trabajo que requiera limpieza, es posible que onUpdate() sea el único método de devolución de llamada que debas definir.

Por ejemplo, si quieres un widget con un botón que inicie una actividad cuando se lo presione, puedes usar la siguiente implementación de AppWidgetProvider:

Kotlin

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        appWidgetIds.forEach { appWidgetId ->
            // Create an Intent to launch ExampleActivity.
            val pendingIntent: PendingIntent = PendingIntent.getActivity(
                    /* context = */ context,
                    /* requestCode = */  0,
                    /* intent = */ Intent(context, ExampleActivity::class.java),
                    /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            val views: RemoteViews = RemoteViews(
                    context.packageName,
                    R.layout.appwidget_provider_layout
            ).apply {
                setOnClickPendingIntent(R.id.button, pendingIntent)
            }

            // Tell the AppWidgetManager to perform an update on the current
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Java

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        for (int i=0; i < appWidgetIds.length; i++) {
            int appWidgetId = appWidgetIds[i];
            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(
                /* context = */ context,
                /* requestCode = */ 0,
                /* intent = */ intent,
                /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
            );

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

Este AppWidgetProvider define solo el método onUpdate() y lo usa para crear un PendingIntent que inicie un Activity y lo adjunte al botón del widget mediante setOnClickPendingIntent(int, PendingIntent). Incluye un bucle que se repite en cada entrada en appWidgetIds, que es un arreglo de IDs que identifica cada widget creado por este proveedor. Si el usuario crea más de una instancia del widget, se actualizan todos de forma simultánea. Sin embargo, solo se administra una programación de updatePeriodMillis para todas las instancias del widget. Por ejemplo, si el programa de actualización se define para que se realice cada dos horas y se agrega una segunda instancia del widget una hora después de la primera, ambas se actualizan en el período definido por la primera, y se ignora el segundo período de actualización. Ambos se actualizan cada dos horas, no cada hora.

Consulta la clase de muestra ExampleAppWidgetProvider.java para obtener más detalles.

Cómo recibir intents de transmisión de widgets

AppWidgetProvider es una clase de conveniencia. Si deseas recibir las transmisiones del widget directamente, puedes implementar tu propio BroadcastReceiver o anular la devolución de llamada onReceive(Context,Intent). Los intents que debes tener en cuenta son los siguientes:

Cómo crear el diseño del widget

Debes definir un diseño inicial para tu widget en formato XML y guardarlo en el directorio res/layout/ del proyecto. Consulta los Lineamientos de diseño para obtener más información.

Crear el diseño del widget es sencillo si conoces los diseños. Sin embargo, ten en cuenta que los diseños de widgets se basan en RemoteViews, que no es compatible con todo tipo de diseño o widget de vista. No puedes usar vistas personalizadas ni subclases de las vistas que sean compatibles con RemoteViews.

RemoteViews también admite ViewStub, que es un View invisible de tamaño cero que puedes usar para aumentar de forma diferida los recursos de diseño durante el tiempo de ejecución.

Compatibilidad con el comportamiento con estado

En Android 12, se agrega compatibilidad para comportamientos con estado mediante los siguientes componentes existentes:

El widget todavía no tiene un estado. La app debe almacenar el estado y registrarse para los eventos de cambio de estado.

Ejemplo de widget de lista de compras que muestra el comportamiento con estado
Figura 3: Ejemplo de comportamiento con estado.

En el siguiente ejemplo de código, se muestra cómo implementar estos componentes.

Kotlin

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true)

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2)

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
        R.id.my_checkbox,
        RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
)

Java

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true);

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2);

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
    R.id.my_checkbox,
    RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent));

Proporciona dos diseños: uno orientado a dispositivos que ejecutan Android 12 o versiones posteriores en res/layout-v31 y el otro orientado a Android 11 o versiones anteriores en la carpeta predeterminada res/layout.

Cómo implementar esquinas redondeadas

En Android 12, se introducen los siguientes parámetros del sistema para establecer los radios de las esquinas redondeadas del widget:

En el siguiente ejemplo, se muestra un widget que usa system_app_widget_background_radius para su esquina y system_app_widget_inner_radius para las vistas dentro de este.

Widget que muestra los radios del fondo del widget y las vistas dentro de este
Figura 4: Esquinas redondeadas.

1 Esquina del widget.

2 Esquina de una vista dentro del widget.

Consideraciones importantes para las esquinas redondeadas

  • Los fabricantes de dispositivos y los launchers de terceros pueden anular el parámetro system_app_widget_background_radius para que sea inferior a 28 dp. El parámetro system_app_widget_inner_radius siempre es 8 dp menos que el valor de system_app_widget_background_radius.
  • Si tu widget no usa @android:id/background ni define un fondo que recorta su contenido en función del contorno, con android:clipToOutline establecido en true, el selector identifica automáticamente el fondo y recorta el widget con un rectángulo con esquinas redondeadas de hasta 16 dp. Consulta Cómo asegurarte de que el widget sea compatible con Android 12.

Para garantizar la compatibilidad del widget con versiones anteriores de Android, te recomendamos que definas los atributos personalizados y uses un tema personalizado para anularlos en Android 12, como se muestra en los siguientes ejemplos de archivos en formato XML:

/values/attrs.xml

<resources>
  <attr name="backgroundRadius" format="dimension" />
</resources>

/values/styles.xml

<resources>
  <style name="MyWidgetTheme">
    <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
  </style>
</resources>

/values-31/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
    <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
  </style>
</resources>

/drawable/my_widget_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <corners android:radius="?attr/backgroundRadius" />
  ...
</shape>

/layout/my_widget_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:background="@drawable/my_widget_background" />