Cómo proporcionar diseños de widgets flexibles

Prueba hacerlo con Compose
Jetpack Compose es el kit de herramientas de IU recomendado para Android. Aprende a compilar widgets con las APIs de estilo de Compose.

En esta página, se describen los ajustes para el tamaño de los widgets y la mayor flexibilidad que se introdujo en Android 12 (nivel de API 31). También se detalla cómo determinar un tamaño para tu widget.

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

A partir de Android 12 (nivel de API 31), puedes proporcionar atributos de tamaño más definidos y diseños flexibles. Para ello, sigue estos pasos, como se describe en las siguientes secciones:

  1. Especifica restricciones adicionales para el tamaño del widget.

  2. Proporcionar diseños adaptables o diseños exactos

En versiones anteriores de Android, es posible obtener los rangos de tamaño de un widget con los extras OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH y OPTION_APPWIDGET_MAX_HEIGHT, y luego estimar el tamaño del widget, pero esa lógica no funciona en todas las situaciones. En el caso de los widgets que se orientan a Android 12 o versiones posteriores, recomendamos proporcionar diseños responsivos o exactos.

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

Android 12 agrega APIs que te permiten asegurarte de que el tamaño de tu widget sea más confiable en diferentes dispositivos con distintos 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 objetivo del widget en términos de celdas de la cuadrícula del selector. Si se definen estos atributos, se usan en lugar de minWidth o minHeight.

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

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

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  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 esto no es posible, otra opción es proporcionar diseños basados en el tamaño exacto del widget en el entorno de ejecución, como se describe en esta página.

Esta función permite un escalamiento más fluido y un mejor estado general del sistema, ya que este no tiene que activar 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.

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

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

    appWidgetManager.updateAppWidget(id, remoteViews)
}

Supongamos que el widget tiene los siguientes atributos:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

El fragmento de código anterior significa lo siguiente:

  • smallView admite desde 160 dp (minResizeWidth) × 110 dp (minResizeHeight) hasta 160 dp × 199 dp (siguiente punto de corte: 1 dp).
  • tallView admite desde 160 dp × 200 dp hasta 214 dp (siguiente punto de corte - 1) × 200 dp.
  • wideView admite desde 215 dp × 110 dp (minResizeHeight) hasta 250 dp (maxResizeWidth) × 200 dp (maxResizeHeight).

Tu widget debe admitir el rango de tamaño de minResizeWidth × minResizeHeight a maxResizeWidth × maxResizeHeight. Dentro de ese rango, puedes decidir el punto de corte para cambiar los diseños.

Ejemplo de diseño responsivo
Figura 1: Ejemplo de un diseño responsivo.

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 AppWidgetManager.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.

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

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

Determina un tamaño para el widget

Cada widget debe definir un targetCellWidth y un targetCellHeight para los dispositivos que ejecutan Android 12 o versiones posteriores, o bien un minWidth y un minHeight para todas las versiones de Android, lo que indica la cantidad mínima de espacio que consume de forma predeterminada. Sin embargo, cuando los usuarios agregan un widget a su pantalla principal, este suele ocupar más del ancho y la altura mínimos que especificas.

Las pantallas principales de Android ofrecen a los usuarios una cuadrícula de espacios disponibles en los que pueden colocar widgets e íconos. Esta cuadrícula puede variar según el dispositivo. Por ejemplo, muchos teléfonos ofrecen una cuadrícula de 5 x 4, y las tablets pueden ofrecer una cuadrícula más grande. Cuando se agrega tu widget, se estira para ocupar la cantidad mínima de celdas, de forma horizontal y vertical, que se requieren para satisfacer las restricciones de targetCellWidth y targetCellHeight en dispositivos que ejecutan Android 12 o versiones posteriores, o las restricciones de minWidth y minHeight en dispositivos que ejecutan Android 11 (nivel de API 30) o versiones anteriores.

Las dimensiones de las celdas y los márgenes automáticos varían según el dispositivo. Usa la siguiente tabla para estimar las dimensiones mínimas de tu widget en un teléfono típico con cuadrícula de 5 x 4, según la cantidad de celdas de cuadrícula que quieras ocupar:

Cantidad de celdas (ancho x alto) Tamaño disponible en modo vertical (dp) Tamaño disponible en modo horizontal (dp)
1x1 57 x 102 dp 127 x 51 dp
2 × 1 130 × 102 dp 269 × 51 dp
3 x 1 203 × 102 dp 412 × 51 dp
4 × 1 276 × 102 dp 554 × 51 dp
5x1 349 x 102 dp 697 × 51 dp
5x2 349 x 220 dp 697 × 117 dp
5 x 3 349 × 337 dp 697 × 184 dp
5x4 349 × 455 dp 697 x 250 dp
… … …
n × m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Usa los tamaños de celda del modo Retrato para informar los valores que proporcionas para los atributos minWidth, minResizeWidth y maxResizeWidth. Del mismo modo, usa los tamaños de celda del modo horizontal para informar los valores que proporcionas para los atributos minHeight, minResizeHeight y maxResizeHeight.

Esto se debe a que el ancho de la celda suele ser menor en el modo Retrato que en el modo horizontal y, de manera similar, la altura de la celda suele ser menor en el modo horizontal que en el modo Retrato.

Por ejemplo, si quieres que el ancho de tu widget se pueda cambiar de tamaño a una celda en un Google Pixel 4, debes establecer tu minResizeWidth en 56 dp como máximo para asegurarte de que el valor del atributo minResizeWidth sea menor que 57 dp, ya que una celda tiene al menos 57 dp de ancho en orientación vertical. Del mismo modo, si quieres que la altura del widget se pueda cambiar de tamaño en una celda del mismo dispositivo, debes establecer el atributo minResizeHeight en 50 dp como máximo para asegurarte de que el valor del atributo minResizeHeight sea inferior a 51 dp, ya que una celda tiene al menos 51 dp de altura en el modo horizontal.

Cada widget se puede cambiar de tamaño dentro de los rangos de tamaño entre los atributos minResizeWidth/minResizeHeight y maxResizeWidth/maxResizeHeight, lo que significa que debe adaptarse a cualquier rango de tamaño entre ellos.

Por ejemplo, para establecer el tamaño predeterminado del widget en la posición, puedes configurar los siguientes atributos:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Esto significa que el tamaño predeterminado del widget es de 3 × 2 celdas, como se especifica en los atributos targetCellWidth y targetCellHeight, o de 180 × 110 dp, como se especifica en minWidth y minHeight para dispositivos que ejecutan Android 11 o versiones anteriores. En este último caso, el tamaño en celdas puede variar según el dispositivo.

Además, para establecer los rangos de tamaño admitidos de tu widget, puedes configurar los siguientes atributos:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Como se especifica en los atributos anteriores, el ancho del widget se puede cambiar de 180 dp a 530 dp, y su altura se puede cambiar de 110 dp a 450 dp. Luego, se puede cambiar el tamaño del widget de 3 × 2 a 5 × 2 celdas, siempre que se cumplan las siguientes condiciones:

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(180f, 110f) to smallView,
    SizeF(270f, 110f) to mediumView,
    SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

Supongamos que el widget usa los diseños responsivos definidos en los fragmentos de código anteriores. Esto significa que el diseño especificado como R.layout.widget_weather_forecast_small se usa desde 180 dp (minResizeWidth) x 110 dp (minResizeHeight) hasta 269 x 279 dp (puntos de corte siguientes - 1). De manera similar, R.layout.widget_weather_forecast_medium se usa de 270 dp × 110 dp a 270 dp × 279 dp, y R.layout.widget_weather_forecast_large se usa de 270 dp × 280 dp a 530 dp (maxResizeWidth) × 450 dp (maxResizeHeight).

A medida que el usuario cambia el tamaño del widget, su apariencia cambia para adaptarse a cada tamaño en celdas, como se muestra en los siguientes ejemplos.

Ejemplo de widget del clima en el tamaño de cuadrícula de 3 × 2 más pequeño. La IU muestra
            el nombre de la ubicación (Tokio), la temperatura (14°) y el símbolo que indica
            clima parcialmente nublado.
Figura 2: 3x2 R.layout.widget_weather_forecast_small.

Ejemplo de widget del clima en un tamaño &quot;mediano&quot; de 4 x 2. Cambiar el tamaño del widget de esta manera se basa en toda la IU del tamaño anterior del widget y agrega la etiqueta &quot;Mayormente nublado&quot; y un pronóstico de temperaturas de 4 p.m. a 7 p.m.
Figura 3: 4x2 R.layout.widget_weather_forecast_medium.

Ejemplo de widget del clima en un tamaño &quot;mediano&quot; de 5 × 2. Si cambias el tamaño del widget de esta manera, se obtiene la misma IU que en el tamaño anterior, excepto que se estira una celda para ocupar más espacio horizontal.
Figura 4. 5x2 R.layout.widget_weather_forecast_medium.

Ejemplo de widget del clima en un tamaño &quot;grande&quot; de 5 × 3. Cambiar el tamaño del widget de esta manera se basa en toda la IU de los tamaños de widget anteriores y agrega una vista dentro del widget que contiene un pronóstico del clima para el martes y el miércoles. Símbolos que indican clima soleado o lluvioso,
            y temperaturas máximas y mínimas para cada día.
Figura 5: 5×3 R.layout.widget_weather_forecast_large.

Ejemplo de widget del clima en un tamaño &quot;grande&quot; de 5 × 4. Cambiar el tamaño del widget de esta manera se basa en toda la IU de los tamaños de widget anteriores y agrega el jueves y el viernes (y sus símbolos correspondientes que indican el tipo de clima, así como la temperatura alta y baja de cada día).
Figura 6. 5 × 4 R.layout.widget_weather_forecast_large.