Mejoras de widgets en Android 12

Android 12 renovó la API de Widgets existente para mejorar la experiencia del usuario y del desarrollador en la plataforma y los launchers. Usa esta guía con el fin de aprender cómo asegurarte de que el widget sea compatible con Android 12 y también como referencia para las API que actualizan el widget existente.

Texto alternativo

Cómo asegurarte de que el widget sea compatible con Android 12

Los widgets en Android 12 tienen esquinas redondeadas. Cuando se usa el widget de una app en un dispositivo que ejecuta Android 12 o versiones posteriores, el launcher identifica automáticamente el fondo del widget y lo recorta para que tenga esquinas redondeadas.

En este caso, es posible que el widget no se muestre de manera correcta en ninguna de las siguientes situaciones:

  • El widget incluye contenido en las esquinas. Es posible que se recorte parte del contenido en el área de la esquina.

  • El widget usa un fondo que no se puede recortar. Incluye un fondo transparente, vistas o diseños vacíos, o cualquier otro tipo de fondo especial que no suela recortarse. Es posible que el sistema no pueda identificar de manera correcta el fondo que se usará.

Si este cambio afecta al widget, te recomendamos que lo actualices con esquinas redondeadas (como se describe en la siguiente sección) para asegurarte de que se muestre de manera correcta.

Cómo usar la muestra

Para ver todas estas API nuevas en funcionamiento, consulta nuestro widget de la lista de muestra.

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.

Texto alternativo

1 Esquina del widget.

2 Esquina de una vista dentro del widget.

Retrocompatibilidad con esquinas redondeadas

A fin de 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" />

Cómo aplicar colores dinámicos

En Android 12, un widget puede usar los colores del tema del dispositivo para botones, fondos y otros componentes. De esta manera, se permiten transiciones más fluidas y coherencia en distintos widgets.

En el siguiente ejemplo, el color de tema del dispositivo es "marrón", lo que permite que se adapten el color de los elementos destacados y el fondo del widget. Para lograrlo, usa el tema predeterminado del sistema (@android:style/Theme.DeviceDefault.DayNight) y sus atributos de color. Estos son algunos de los atributos de color de uso general:

  • ?android:attr/colorAccent
  • ?android:attr/colorBackground
  • ?android:attr/textColorPrimary y ?android:attr/textColorSecondary
Widget en el tema del modo claro
Widget en el tema claro
Widgets en el tema del modo oscuro
Widget en el tema oscuro

Retrocompatibilidad con colores dinámicos

Te recomendamos que crees un tema personalizado y lo anules para Android 12. En los siguientes ejemplos, se muestra cómo hacerlo con varios tipos de archivos en formato XML:

/values/styles.xml

<resources>
  <style name="MyWidget.TextView">
    <item name="android:textColor">@color/my_text_color</item>
  </style>
  <style name="MyWidgetTheme">
    <item name="textViewStyle">@style/MyWidget.TextView</item>
  </style>
</resources>

/values-31/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="Theme.DeviceDefault.DayNight" />
</resources>

/layout/my_widget_layout.xml

<resources>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:theme="@style/MyWidgetTheme" />
</resources>

Cómo personalizar widgets con mayor facilidad

Si especificas una actividad de configuración con el atributo configure de appwidget-provider, el host de widgets de la app inicia esa actividad inmediatamente después de que el usuario agregue el widget a su pantalla principal.

En Android 12, se agregan opciones nuevas para que puedas ofrecerles una mejor experiencia de configuración a los usuarios.

Cómo permitir que los usuarios reconfiguren los widgets colocados

Para configurar los widgets que se etiquetan como reconfigurables, los usuarios pueden mantenerlos presionados. Se muestra un botón Reconfigurar, que pueden presionar para cambiar la configuración.

Texto alternativo

1 Botón Reconfigurar.

Especifica la marca reconfigurable en el atributo widgetFeatures de appwidget-provider:

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable">
</appwidget-provider>

Cómo usar la configuración predeterminada del widget

Si deseas que el widget use su configuración predeterminada cuando un usuario lo agregue, puedes omitir el paso de configuración si especificas las marcas configuration_optional y reconfigurable en el campo widgetFeatures. De esta manera, se evita iniciar la actividad de configuración después de que un usuario agrega el widget. (Como se mencionó anteriormente, el usuario todavía puede reconfigurar el widget más adelante).

Por ejemplo, un widget de reloj podría evitar la configuración inicial y mostrar la zona horaria del dispositivo de forma predeterminada.

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Retrocompatibilidad con opciones de configuración de widgets

La app puede usar las marcas configuration_optional y reconfigurable en versiones anteriores de Android. Sin embargo, estas marcas no producirán ningún efecto, y el sistema, de todos modos, iniciará la actividad de configuración.

Cómo agregar botones compuestos nuevos

En Android 12, se agrega compatibilidad nueva para comportamientos con estados 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.

Texto alternativo

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 will have 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 will have 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)
);

Retrocompatibilidad con botones compuestos de widgets

Brinda dos diseños diferentes: uno se orienta a dispositivos que ejecutan Android 12 o versiones posteriores (res/layout-v31) y el otro se orienta a versiones anteriores de Android (en la carpeta res/layout predeterminada).

Cómo usar API mejoradas para tamaños y diseños de widgets

A partir de Android 12, para brindar atributos de tamaño más definidos y diseños más flexibles, haz lo siguiente:

  1. Especifica restricciones adicionales para el tamaño de widgets

  2. Brinda diseños responsivos o exactos.

Cómo especificar restricciones adicionales para el tamaño de widgets

En Android 12, se agregan API nuevas que permiten garantizar que se establezca de manera más confiable el tamaño del widget en distintos dispositivos con diferentes tamaños de pantalla.

Además de los atributos existentes minWidth, minHeight, minResizeWidth y minResizeHeight, usa los siguientes atributos appwidget-provider nuevos:

  • targetCellWidth y targetCellHeight: Definen el tamaño de destino del widget en términos de las celdas de cuadrícula del launcher. Si se definen estos atributos, se usan en lugar de minWidth o minHeight.

  • maxResizeWidth y maxResizeHeight: Definen el tamaño máximo del launcher que le permite al usuario cambiar el tamaño del widget.

En el siguiente XML, se describe cómo usar los atributos de tamaño.

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

Cómo brindar diseños responsivos

Si el diseño necesita cambiar en función del tamaño del widget, te recomendamos que crees un conjunto pequeño de diseños y que cada uno sea válido para una variedad de tamaños. (Si no es posible, otra opción es brindar diseños según el tamaño exacto del widget en el tiempo de ejecución).

Implementar esta función permite un escalamiento más fluido y un mejor estado general del sistema, ya que no es necesario que el sistema active la app cada vez que muestra el widget en un tamaño diferente.

En el siguiente ejemplo de código, se muestra cómo brindar una lista de diseños.

Kotlin

override fun onUpdate(...) {
  val smallView = ...
  val tallView = ...
  val wideView = ...

  val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(100f, 100f) to smallView,
    SizeF(100f, 200f) to tallView,
    SizeF(200f, 100f) to wideView
  )
  val remoteViews = RemoteViews(viewMapping)

  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
  RemoteViews smallView = ...;
  RemoteViews tallView = ...;
  RemoteViews wideView = ...;

  Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
  viewMapping.put(new SizeF(100f, 100f), smallView);
  viewMapping.put(new SizeF(100f, 200f), tallView);
  viewMapping.put(new SizeF(200f, 100f), wideView);
  RemoteViews remoteViews = new RemoteViews(viewMapping);

  appWidgetManager.updateAppWidget(id, remoteViews);
}

Cómo brindar diseños exactos

Si no es posible proporcionar un conjunto pequeño de diseños responsivos, en su lugar, puedes brindar diferentes diseños que se adapten a los tamaños en los que se muestra el widget. Suelen ser dos tamaños para teléfonos (modo de retrato y de paisaje) y cuatro tamaños para dispositivos plegables.

Para implementar esta solución, la app debe realizar los siguientes pasos:

  1. Sobrecargar a AppWidgetProvider#onAppWidgetOptionsChanged(...), al que se llama cuando cambia el conjunto de tamaños.

  2. Llamar a getAppWidgetManager#getAppWidgetOptions(...), que muestra Bundle con los tamaños.

  3. Acceder a la clave AppWidgetManager.OPTION_APPWIDGET_SIZES desde Bundle.

En el siguiente ejemplo de código, se muestra cómo brindar diseños exactos.

Kotlin

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

override fun onAppWidgetOptionsChanged(
  context: Context,
  manager: AppWidgetManager,
  id: Int,
  newOptions: Bundle?
) {
  super.onAppWidgetOptionsChanged(context, manager, id, newOptions)
  // Get the new sizes.
  val sizes = newOptions?.getParcelableArrayList<SizeF>(
    AppWidgetManager.OPTION_APPWIDGET_SIZES
  )
  // Check that the list of sizes is provided by the launcher.
  if (sizes.isNullOrEmpty()) {
    return
  }
  // Map the sizes to the desired RemoteViews
  val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager
  appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
      newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the desired RemoteViews.
    Map<SizesF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
      viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

Retrocompatibilidad con tamaños de diseño de widgets

Anteriormente, era posible obtener los rangos de tamaño del widget con los valores adicionales OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH y OPTION_APPWIDGET_MAX_HEIGHT, y estimar el tamaño de este, pero esa lógica no funciona en todas las situaciones. Para los widgets que se orientan a Android 12, te recomendamos que comiences a brindar diseños responsivos o exactos, como se explicó anteriormente.

Cómo mejorar la experiencia con el selector del widget de la app

En Android 12, a fin de mejorar la experiencia del selector del widget para la app, puedes agregar vistas previas dinámicas y descripciones de widgets.

Cómo agregar vistas previas escalables de widgets al selector del widget

En Android 12, la vista previa del widget que se muestra en el selector del widget consiste en una vista previa escalable, que brindarás como un diseño XML configurado en el tamaño predeterminado del widget. Anteriormente, la vista previa del widget era un recurso estático de elementos de diseño que, en algunos casos, causaba que las vistas previas no mostraran con exactitud los widgets después de que se agregaban a la pantalla principal.

A fin de implementar las vistas previas escalables de widgets, usa el atributo previewLayout del elemento appwidget-provider para brindar un diseño XML en su lugar:

<appwidget-provider
  ...
  android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>

Lo ideal es que este diseño sea el mismo que el del widget real con valores realistas predeterminados o de las pruebas.

Retrocompatibilidad con vistas previas escalables de widgets

A fin de habilitar los selectores del widget en Android 11 o versiones anteriores para mostrar vistas previas de este, continúa especificando el atributo previewImage.

Si realizas cambios en el aspecto del widget, asegúrate de actualizar también la imagen de la vista previa.

Cómo agregar una descripción para el widget

En Android 12, tienes la opción de brindar una descripción para que el selector del widget se muestre con este.

Texto alternativo

Brinda una descripción para el widget con el atributo de descripción de appwidget-provider:

<appwidget-provider
  ...
  android:description="@string/my_widget_description">
</appwidget-provider>

Retrocompatibilidad con descripciones de widgets

La app puede usar el atributo widgetDescription en versiones anteriores de Android, pero no se mostrará en el selector del widget.

Cómo habilitar transiciones más fluidas

En Android 12, los launchers brindan una transición más fluida cuando un usuario inicia la app desde un widget.

A fin de habilitar la mejora de esta transición, usa @android:id/background o android.R.id.background para identificar el elemento en segundo plano:

// Top level layout of the widget.
<LinearLayout
  ...
  android:id="@android:id/background">
</LinearLayout>

Retrocompatibilidad con transiciones más fluidas

La app puede usar @android:id/background en versiones anteriores de Android, pero no producirá ningún efecto.

Cómo usar colecciones RemoteViews simplificadas

En Android 12, se agrega el método setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items), que le permite a la app pasar una colección directamente cuando se propaga ListView. Anteriormente, cuando se usaba ListView, era necesario implementar y declarar RemoteViewsService para mostrar RemoteViewsFactory.

Si la colección no usa un conjunto constante de diseños (en otras palabras, si algunos elementos solo están presentes), usa setViewTypeCount para especificar la cantidad máxima de diseños únicos que puede incluir la colección.

A continuación, se muestra un ejemplo de cómo implementar colecciones RemoteViews simplificadas.

Kotlin

remoteView.setRemoteAdapter(
  R.id.list_view,
  RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, RemoteViews(...))
    .addItem(/* id= */ ID_2, RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
)

Java

remoteView.setRemoteAdapter(
  R.id.list_view,
  new RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, new RemoteViews(...))
    .addItem(/* id= */ ID_2, new RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
);

Cómo utilizar la modificación del tiempo de ejecución de RemoteViews

En Android 12, se agregan varios métodos RemoteViews que permiten la modificación del tiempo de ejecución de los atributos RemoteViews. Consulta la referencia de la API de RemoteViews para ver la lista completa de métodos agregados.

En el siguiente ejemplo de código, se muestra cómo usar algunos de los métodos nuevos.

Kotlin

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList())

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP)

Java

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList());

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP);