Fournir des mises en page de widget flexibles

Cette page décrit les améliorations apportées au dimensionnement des widgets et leur flexibilité accrue. introduit 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 indiquer une taille plus précise. et les mises en page flexibles, procédez comme suit, comme décrit dans les suivantes:

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

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

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

Spécifier des contraintes de dimensionnement supplémentaires pour les widgets

Android 12 ajoute des API qui vous permettent de vous assurer que votre widget est dimensionnés de manière plus fiable sur des appareils différents avec des tailles d'écran différentes.

En plus du minWidth existant, 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 grille du lanceur. Si défini, ces attributs sont utilisés à la place de minWidth ou minHeight.

  • maxResizeWidth et maxResizeHeight: définir la taille maximale à laquelle le lanceur d'applications permet à l'utilisateur de redimensionner le widget.

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 valide pour une plage de tailles. Si ce 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 le système n'a pas besoin de réveiller 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 présente 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 une résolution de 160 dp (minResizeWidth) × 110 dp. (minResizeHeight) à 160 dp × 199 dp (prochain point limite : 1 dp).
  • tallView prend en charge de 160 dp × 200 dp à 214 dp (limite suivante - 1) × 200dp.
  • wideView est compatible avec les tailles de 215 dp x 110 dp (minResizeHeight) à 250 dp (maxResizeWidth) x 200 dp (maxResizeHeight).

Votre widget doit prendre en charge la plage de tailles 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 possible, vous pouvez fournir différentes mises en page adaptées aux tailles à laquelle le widget s'affiche. C'est généralement deux tailles pour les téléphones (mode portrait et paysage) et quatre tailles pour appareils pliables.

Pour implémenter cette solution, votre application doit effectuer les étapes suivantes:

  1. Surchargez AppWidgetProvider.onAppWidgetOptionsChanged(), qui est appelé 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 une taille pour 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 minWidth et 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 des espaces disponibles sur lesquels ils peuvent placer des widgets et des icônes. Cette grille peut varier selon l'appareil : par exemple, de nombreuses les téléphones ont une grille de 5 x 4 et les tablettes peuvent en offrir une plus grande. Lorsque votre widget est ajoutée, elle est étirée pour occuper le nombre minimal de cellules, horizontalement et verticalement, afin de satisfaire aux contraintes targetCellWidth et targetCellHeight sur les appareils en cours d'exécution Android 12 ou version ultérieure, ou contraintes minWidth et minHeight pour appareils équipés d'Android 11 (niveau d'API 30) ou version antérieure.

La largeur et la hauteur d'une cellule et la taille des marges automatiques appliquées aux widgets peut varier d'un appareil à l'autre. Utilisez le tableau suivant pour estimer grossièrement les dimensions minimales de votre widget dans un combiné classique à grille de 5 x 4, compte tenu de la nombre de cellules de grille occupées que vous souhaitez:

Nombre de cellules (largeur x hauteur) Taille disponible en mode portrait (dp) Taille disponible en mode paysage (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554 x 51 dp
5x1 349x102dp 697x51dp
5x2 349 x 220 dp 697x117dp
5x3 349x337dp 697 x 184 dp
5x4 349x455dp 697x250dp
... ...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Utilisez les tailles de cellules en 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 en mode paysage pour déterminer les valeurs que vous fournissez pour les attributs minHeight, minResizeHeight et maxResizeHeight.

En effet, la largeur de la cellule est généralement plus petite en mode portrait qu'en mode paysage. De même, la hauteur de la cellule est généralement plus petite en mode paysage qu'en mode portrait.

Par exemple, si vous voulez que la largeur de votre widget puisse être redimensionnée pour atteindre une seule cellule un Google Pixel 4, la résolution de votre minResizeWidth ne doit pas dépasser 56 dp pour vous assurer que la valeur de l'attribut minResizeWidth est plus petite de 57 dp, car la largeur d'une cellule en mode portrait est d'au moins 57 dp. 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 votre minResizeHeight sur 50 dp au maximum pour vous assurer que la valeur de l'attribut minResizeHeight est inférieure à 51 dp, car une cellule mesure au moins 51 dp en mode paysage.

Chaque widget peut être redimensionné dans les plages de tailles comprises entre les minResizeWidth/minResizeHeight et maxResizeWidth/maxResizeHeight ce qui signifie qu'il doit s'adapter à toutes les plages de tailles qui les séparent.

Par exemple, pour définir la taille par défaut du widget lors de l'emplacement, vous pouvez Définissez 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 des cellules peut varier en fonction de l'appareil.

Pour définir les plages de tailles compatibles de votre widget, vous pouvez également définir les attributs suivants :

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

Comme indiqué par les attributs précédents, la largeur du widget est de 180 dp à 530 dp, et sa hauteur peut être redimensionnée de 110 dp à 450 dp. Il est alors possible de redimensionner les cellules de 3 x 2 à 5 x 2, à condition que les conditions suivantes sont présentes:

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) à 269x279 dp (points de coupure suivants - 1). De même, R.layout.widget_weather_forecast_medium est utilisé entre 270 x 110 dp et 270 x 279 dp. et R.layout.widget_weather_forecast_large est utilisé pour les valeurs comprises entre 270 x 280 dp et 530dp (maxResizeWidth) x 450dp (maxResizeHeight).

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

Exemple de widget météo dans la plus petite taille de grille 3 x 2 L&#39;UI montre
            le nom du lieu (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 dans un format &quot;medium&quot; 4x2 la taille de l&#39;image. Redimensionner le widget de cette manière s&#39;appuie sur l&#39;ensemble de l&#39;interface utilisateur de la taille de widget précédente et ajoute le libellé &quot;Partiellement nuageux&quot; et une prévision des températures de 16h à 19h.
Figure 3 R.layout.widget_weather_forecast_medium 4 x 2.

Exemple de widget météo dans un élément &quot;medium&quot; 5x2 la taille de l&#39;image. Redimensionner le widget
            Cela donne la même interface utilisateur que la taille précédente, sauf qu&#39;il s&#39;agit
            étirée d’une longueur de cellule
pour occuper plus d’espace horizontal.
Figure 4. 5x2 R.layout.widget_weather_forecast_medium.

Exemple de widget météo dans un grand modèle 5x3 &quot;large&quot; la taille de l&#39;image. Redimensionner le widget de cette manière s&#39;appuie sur l&#39;ensemble de l&#39;interface utilisateur des tailles de widget précédentes et ajoute une vue dans le widget contenant les prévisions météo pour le mardi et le mercredi. Des symboles indiquant le temps ensoleillé ou pluvieux, ainsi que les températures maximale et minimale pour chaque jour.
Figure 5. 5x3 R.layout.widget_weather_forecast_large.

Exemple de widget météo en taille &quot;L&quot; 5 x 4 la taille de l&#39;image. Redimensionner le widget
            de cette façon, s&#39;appuie sur toutes les interfaces
des tailles de widget précédentes,
            et ajoute les jeudis et vendredis (et leurs symboles correspondants)
            indiquant le type de météo ainsi que les températures maximale et basse
            pour chaque jour).
Figure 6. R.layout.widget_weather_forecast_large 5 x 4.