Cómo proporcionar diseños de widgets flexibles

En esta página, se describen mejoras para el tamaño de widgets y una 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 diseños flexibles y atributos de tamaño más definidos. Para ello, haz lo siguiente, como se describe en las siguientes secciones:

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

  2. Proporcionar diseños responsivos o exactos

En las 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, te recomendamos que proporciones diseños responsivos o exactos.

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

En Android 12, se agregan APIs que te permiten garantizar que el tamaño del widget sea más confiable en diferentes 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 selector. Si se definen estos atributos, se usan en lugar de minWidth o minHeight.

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

En el siguiente XML, se muestra 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 esto no es posible, otra opción es proporcionar diseños según el tamaño exacto del widget durante el tiempo 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 el sistema no necesita 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.

Kotlin

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

    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)
}

Java

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

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

    appWidgetManager.updateAppWidget(id, remoteViews);
}

Supongamos que el widget tiene los siguientes atributos:

<appwidget-provider
    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 (próximo 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 un rango de tamaño entre minResizeWidth × minResizeHeight y maxResizeWidth × maxResizeHeight. Dentro de ese rango, puedes decidir el punto de corte para cambiar de diseño.

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.

Kotlin

override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        id: Int,
        newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, 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 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 { }

Java

@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 RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

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

Determina un tamaño para tu widget

Cada widget debe definir un targetCellWidth y un targetCellHeight para los dispositivos que ejecutan Android 12 o versiones posteriores, o bien minWidth y 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, por lo general, ocupa más que el ancho y la altura mínimos que especificaste.

Las pantallas principales de Android ofrecen a los usuarios una cuadrícula de espacios disponibles en los que pueden colocar íconos y widgets. Esa 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 el widget, se expande para ocupar la cantidad mínima de celdas, horizontal y vertical, necesaria para cumplir con las restricciones de targetCellWidth y targetCellHeight en dispositivos que ejecutan Android 12 o versiones posteriores, o las restricciones de minWidth y minHeight en dispositivos con Android 11 (nivel de API 30) o versiones anteriores.

El ancho y la altura de una celda, y el tamaño de los márgenes automáticos aplicados a los widgets pueden variar según el dispositivo. Usa la siguiente tabla para estimar aproximadamente las dimensiones mínimas de tu widget en un teléfono celular con cuadrícula típico de 5 × 4, según la cantidad de celdas de cuadrícula ocupadas que desees:

Número de celdas (ancho x alto) Tamaño disponible en modo vertical (dp) Tamaño disponible en modo horizontal (dp)
1x1 57 × 102 dp 127x51dp
2 × 1 130x102dp 269x51dp
3 × 1 203x102dp 412x51dp
4 × 1 276x102dp 554x51dp
5 × 1 349x102dp 697x51dp
5 × 2 349x220dp 697x117dp
5 × 3 349x337dp 697x184dp
5 × 4 349x455dp 697x250dp
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Usa los tamaños de celda de modo vertical para informar los valores que proporcionas para los atributos minWidth, minResizeWidth y maxResizeWidth. De manera similar, usa los tamaños de celda de modo horizontal para indicar 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 vertical que en el modo horizontal y, del mismo modo, la altura de la celda suele ser menor en el modo horizontal que en el modo vertical.

Por ejemplo, si quieres que se pueda cambiar el tamaño del widget hasta 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 inferior a 57 dp, ya que la celda tiene al menos 57 dp de ancho en modo vertical. Del mismo modo, si deseas que se pueda cambiar el tamaño del widget en una celda del mismo dispositivo, debes configurar 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 alto en modo horizontal.

Se puede cambiar el tamaño de cada widget 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 configurar el tamaño predeterminado del widget en la posición, puedes configurar los siguientes atributos:

<appwidget-provider
    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 x 2 celdas, como lo especifican los atributos targetCellWidth y targetCellHeight, o 180 × 110 dp, según lo que especifican minWidth y minHeight para los dispositivos que ejecutan Android 11 o versiones anteriores. En el último caso, el tamaño en las celdas puede variar según el dispositivo.

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

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

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

Kotlin

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))

Java

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

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 de 180 dp (minResizeWidth) x 110 dp (minResizeHeight) a 269 x 279 dp (próximos puntos de corte: 1). Del mismo modo, se usa R.layout.widget_weather_forecast_medium de 270 x 110 dp a 270 x 279 dp, y R.layout.widget_weather_forecast_large se usa de 270 x 280 dp a 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

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

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

Ejemplo de widget del clima en un tamaño “mediano” de 4 × 2. El cambio de tamaño del widget de esta manera se basa en toda la IU del tamaño anterior y agrega la etiqueta &quot;Mayormente nublado&quot; y un pronóstico de temperaturas desde las 4 p.m. hasta las 7 p.m.
Figura 3: 4 × 2 R.layout.widget_weather_forecast_medium.

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

Ejemplo de widget del clima en un tamaño “grande” de 5 × 3 El cambio de tamaño del widget de esta manera se basa en toda la IU de los tamaños anteriores del widget y se agrega una vista dentro del widget que contiene un pronóstico del tiempo el martes y miércoles. Símbolos que indican el clima soleado o lluvioso y las temperaturas altas y bajas para cada día
Figura 5: 5 × 3 R.layout.widget_weather_forecast_large.

Ejemplo de widget del clima en un tamaño “grande” de 5 × 4 Este cambio de tamaño del widget se basa en toda la IU de los tamaños anteriores y agrega los días jueves y viernes (y sus símbolos correspondientes que indican el tipo de clima, así como las temperaturas máxima y mínima de cada día).
Figura 6: 5 × 4 R.layout.widget_weather_forecast_large.