Fournir des mises en page de widget flexibles

Essayer Compose
Jetpack Compose est le kit d'outils d'UI recommandé pour Android. Découvrez comment créer des widgets à l'aide d'API de style Compose.

Cette page décrit les améliorations apportées à la taille des widgets et la flexibilité accrue introduites dans Android 12 (niveau d'API 31). Il explique également comment déterminer la taille de votre widget.

Utiliser des API améliorées pour les tailles et les mises en page des widgets

À partir d'Android 12 (niveau d'API 31), vous pouvez fournir des attributs de taille plus précis et des mises en page flexibles en procédant comme suit, comme décrit dans les sections suivantes :

  1. Spécifiez des contraintes de dimensionnement supplémentaires pour le widget.

  2. Fournir des mises en page responsives ou des mises en page exactes.

Dans les versions précédentes d'Android, il était possible d'obtenir les plages de taille d'un widget à l'aide des extras OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH et OPTION_APPWIDGET_MAX_HEIGHT, puis d'estimer la taille du widget, mais cette logique ne fonctionne pas dans toutes les situations. Pour les widgets ciblant Android 12 ou version ultérieure, nous vous recommandons de fournir des mises en page responsives ou exactes.

Spécifier des contraintes de dimensionnement de widget supplémentaires

Android 12 ajoute des API qui vous permettent de vous assurer que la taille de votre widget est plus fiable sur différents appareils avec des tailles d'écran variables.

En plus des attributs existants minWidth, minHeight, minResizeWidth et minResizeHeight, utilisez les nouveaux attributs appwidget-provider suivants :

  • targetCellWidth et targetCellHeight : définissent la taille cible du widget en termes de cellules de la grille du lanceur d'applications. S'ils sont définis, ces attributs sont utilisés à la place de minWidth ou minHeight.

  • maxResizeWidth et maxResizeHeight : définissent la taille maximale à laquelle l'utilisateur peut redimensionner le widget dans le lanceur d'applications.

Le code XML suivant montre comment utiliser les attributs de dimensionnement.

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

Fournir des mises en page responsives

Si la mise en page doit changer en fonction de la taille du widget, nous vous recommandons de créer un petit ensemble de mises en page, chacune étant valable pour une plage de tailles. Si cela n'est pas possible, vous pouvez également fournir des mises en page basées sur la taille exacte du widget au moment de l'exécution, comme décrit sur cette page.

Cette fonctionnalité permet une mise à l'échelle plus fluide et une meilleure santé globale du système, car celui-ci n'a pas besoin de réactiver l'application chaque fois qu'il affiche le widget dans une taille différente.

L'exemple de code suivant montre comment fournir une liste de mises en page.

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

Supposons que le widget possède les attributs suivants :

<appwidget-provider
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

L'extrait de code précédent signifie ce qui suit :

  • smallView est compatible avec les dimensions allant de 160 dp (minResizeWidth) × 110 dp (minResizeHeight) à 160 dp × 199 dp (prochaine limite – 1 dp).
  • tallView est compatible avec les dimensions allant de 160 dp × 200 dp à 214 dp (point d'arrêt suivant - 1) × 200 dp.
  • wideView prend en charge les dimensions allant de 215 dp × 110 dp (minResizeHeight) à 250 dp (maxResizeWidth) × 200 dp (maxResizeHeight).

Votre widget doit être compatible avec la plage de tailles allant de minResizeWidth × minResizeHeight à maxResizeWidth × maxResizeHeight. Dans cette plage, vous pouvez choisir le point de coupure pour changer de mise en page.

Exemple de mise en page responsive
Figure 1 Exemple de mise en page responsive.

Fournir des mises en page exactes

Si un petit ensemble de mises en page responsives n'est pas réalisable, vous pouvez à la place fournir différentes mises en page adaptées aux tailles auxquelles le widget est affiché. Il s'agit généralement de deux tailles pour les téléphones (mode Portrait et Paysage) et de quatre tailles pour les appareils pliables.

Pour mettre en œuvre cette solution, votre application doit effectuer les étapes suivantes :

  1. Surcharge AppWidgetProvider.onAppWidgetOptionsChanged(), appelée lorsque l'ensemble de tailles change.

  2. Appelez AppWidgetManager.getAppWidgetOptions(), qui renvoie un Bundle contenant les tailles.

  3. Accédez à la clé AppWidgetManager.OPTION_APPWIDGET_SIZES à partir de Bundle.

L'exemple de code suivant montre comment fournir des mises en page exactes.

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

Déterminer la taille de votre widget

Chaque widget doit définir un targetCellWidth et un targetCellHeight pour les appareils équipés d'Android 12 ou version ultérieure, ou un minWidth et un minHeight pour toutes les versions d'Android, indiquant la quantité minimale d'espace qu'il consomme par défaut. Toutefois, lorsque les utilisateurs ajoutent un widget à leur écran d'accueil, il occupe généralement plus que la largeur et la hauteur minimales que vous spécifiez.

Les écrans d'accueil Android proposent aux utilisateurs une grille d'espaces disponibles sur lesquels ils peuvent placer des widgets et des icônes. Cette grille peut varier selon l'appareil. Par exemple, de nombreux téléphones portables proposent une grille 5x4, tandis que les tablettes peuvent proposer une grille plus grande. Lorsque votre widget est ajouté, il est étiré pour occuper le nombre minimal de cellules, horizontalement et verticalement, requis pour satisfaire les contraintes de ses targetCellWidth et targetCellHeight sur les appareils équipés d'Android 12 ou version ultérieure, ou les contraintes minWidth et minHeight sur les appareils équipés d'Android 11 (niveau d'API 30) ou version antérieure.

La largeur et la hauteur d'une cellule, ainsi que la taille des marges automatiques appliquées aux widgets, peuvent varier d'un appareil à l'autre. Utilisez le tableau suivant pour estimer approximativement les dimensions minimales de votre widget dans un téléphone à grille 5x4 typique, en fonction du nombre de cellules de grille occupées souhaité :

Nombre de cellules (largeur x hauteur) Taille disponible en mode Portrait (dp) Taille disponible en mode paysage (dp)
1x1 57 x 102 dp 127x51dp
2x1 130 x 102 dp 269 x 51 dp
3x1 203 x 102 dp 412 x 51 dp
4x1 276 x 102 dp 554 x 51 dp
5x1 349 x 102 dp 697 x 51 dp
5x2 349 x 220 dp 697 x 117 dp
5x3 349 x 337 dp 697 x 184 dp
5x4 349 x 455 dp 697 x 250 dp
... ...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Utilisez les tailles de cellules du mode Portrait pour déterminer les valeurs que vous fournissez pour les attributs minWidth, minResizeWidth et maxResizeWidth. De même, utilisez les tailles de cellules du mode paysage pour déterminer les valeurs que vous fournissez pour les attributs minHeight, minResizeHeight et maxResizeHeight.

En effet, la largeur des cellules est généralement plus petite en mode Portrait qu'en mode Paysage. De même, la hauteur des cellules est généralement plus petite en mode Paysage qu'en mode Portrait.

Par exemple, si vous souhaitez que la largeur de votre widget soit redimensionnable jusqu'à une cellule sur un Google Pixel 4, vous devez définir minResizeWidth sur 56 dp maximum pour vous assurer que la valeur de l'attribut minResizeWidth est inférieure à 57 dp, car une cellule a une largeur d'au moins 57 dp en mode Portrait. De même, si vous souhaitez que la hauteur de votre widget soit redimensionnable dans une cellule sur le même appareil, vous devez définir minResizeHeight sur 50 dp maximum pour vous assurer que la valeur de l'attribut minResizeHeight est inférieure à 51 dp, car une cellule a une hauteur d'au moins 51 dp en mode paysage.

Chaque widget est redimensionnable dans les plages de tailles comprises entre les attributs minResizeWidth/minResizeHeight et maxResizeWidth/maxResizeHeight, ce qui signifie qu'il doit s'adapter à toutes les plages de tailles comprises entre eux.

Par exemple, pour définir la taille par défaut du widget sur l'emplacement, vous pouvez définir les attributs suivants :

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Cela signifie que la taille par défaut du widget est de 3 x 2 cellules, comme spécifié par les attributs targetCellWidth et targetCellHeight, ou de 180 x 110 dp, comme spécifié par minWidth et minHeight pour les appareils équipés d'Android 11 ou version antérieure. Dans ce dernier cas, la taille en cellules peut varier en fonction de l'appareil.

De plus, pour définir les plages de tailles compatibles de votre widget, vous pouvez définir les attributs suivants :

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

Comme spécifié par les attributs précédents, la largeur du widget est redimensionnable de 180 dp à 530 dp, et sa hauteur est redimensionnable de 110 dp à 450 dp. Le widget peut ensuite être redimensionné de 3x2 à 5x2 cellules, à condition que les conditions suivantes soient remplies :

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

Supposons que le widget utilise les mises en page responsives définies dans les extraits de code précédents. Cela signifie que la mise en page spécifiée comme R.layout.widget_weather_forecast_small est utilisée de 180 dp (minResizeWidth) x 110 dp (minResizeHeight) à 269 x 279 dp (points d'arrêt suivants - 1). De même, R.layout.widget_weather_forecast_medium est utilisé de 270x110dp à 270x279dp, et R.layout.widget_weather_forecast_large est utilisé de 270x280dp à 530dp (maxResizeWidth) x 450dp (maxResizeHeight).

Lorsque l'utilisateur redimensionne le widget, son apparence change pour s'adapter à chaque taille en cellules, comme illustré dans les exemples suivants.

Exemple de widget météo dans la plus petite taille de grille 3x2. L&#39;UI affiche le nom de la ville (Tokyo), la température (14 °) et un symbole indiquant un temps partiellement nuageux.
Figure 2 3x2 R.layout.widget_weather_forecast_small.

Exemple de widget météo de taille &quot;moyenne&quot; (4x2). Le redimensionnement du widget de cette manière s&#39;appuie sur l&#39;ensemble de l&#39;UI de la taille de widget précédente, et ajoute le libellé &quot;Nuageux&quot; et une prévision des températures de 16h à 19h.
Figure 3 4x2 R.layout.widget_weather_forecast_medium.

Exemple de widget météo de taille &quot;moyenne&quot; (5x2). Le redimensionnement du widget de cette manière donne la même UI que la taille précédente, sauf qu&#39;elle est étirée d&#39;une longueur de cellule pour occuper plus d&#39;espace horizontal.
Figure 4. 5x2 R.layout.widget_weather_forecast_medium.

Exemple de widget météo de taille &quot;grande&quot; (5x3). Le redimensionnement du widget de cette manière s&#39;appuie sur l&#39;ensemble de l&#39;UI des tailles de widget précédentes et ajoute une vue à l&#39;intérieur du widget contenant une prévision météo pour mardi et mercredi. Des symboles indiquant le temps ensoleillé ou pluvieux, ainsi que les températures maximales et minimales pour chaque jour.
Figure 5. 5x3 R.layout.widget_weather_forecast_large.

Exemple de widget météo de taille &quot;grande&quot; (5x4). Le redimensionnement du widget de cette manière s&#39;appuie sur l&#39;ensemble de l&#39;UI des tailles de widget précédentes et ajoute le jeudi et le vendredi (ainsi que leurs symboles correspondants indiquant le type de temps, ainsi que les températures maximales et minimales pour chaque jour).
Figure 6. 5x4 R.layout.widget_weather_forecast_large.