Créer un widget d'application avec Glance

Les sections suivantes expliquent comment créer un widget d'application de base avec Glance.

Déclarer le AppWidget dans le fichier manifeste

Après avoir suivi la procédure de configuration, déclarez le AppWidget et ses métadonnées dans votre application.

  1. Étendez le récepteur AppWidget à partir de GlanceAppWidgetReceiver :

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. Enregistrez le fournisseur du widget d'application dans votre fichier AndroidManifest.xml et le fichier de métadonnées associé :

        <receiver android:name=".glance.MyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_info" />
    </receiver>
    

Ajouter les métadonnées AppWidgetProviderInfo

Ensuite, suivez le guide Créer un widget pour créer et définir les informations du widget d'application dans le fichier @xml/my_app_widget_info.

La seule différence pour Glance est qu'il n'y a pas de fichier XML initialLayout, mais que vous devez en définir un. Vous pouvez utiliser la mise en page de chargement prédéfinie fournie dans la bibliothèque :

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

Déclarer le fichier XML AppWidgetProviderInfo

L'objet AppWidgetProviderInfo définit les qualités essentielles de votre widget. Définissez AppWidgetProviderInfo dans votre fichier de ressources de métadonnées XML (res/xml/my_app_widget_info.xml) dans un élément <appwidget-provider> :

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Attributs de dimensionnement des widgets

L'écran d'accueil par défaut positionne les widgets dans sa fenêtre en fonction d'une grille de cellules dont la hauteur et la largeur sont définies. La plupart des écrans d'accueil n'autorisent que les widgets dont la taille est un multiple entier des cellules de la grille (par exemple, deux cellules horizontalement et trois cellules verticalement).

Les attributs de dimensionnement du widget vous permettent de spécifier une taille par défaut pour votre widget et de définir des limites inférieure et supérieure pour la taille du widget. Dans ce contexte, la taille par défaut d'un widget correspond à la taille qu'il prend lorsqu'il est ajouté pour la première fois à l'écran d'accueil.

Le tableau suivant décrit les attributs <appwidget-provider> relatifs au dimensionnement des widgets :

Attributs et description
targetCellWidth et targetCellHeight (Android 12), minWidth et minHeight
  • À partir d'Android 12, les attributs targetCellWidth et targetCellHeight spécifient la taille par défaut du widget en termes de cellules de grille. Ces attributs sont ignorés sous Android 11 ou version antérieure, et peuvent être ignorés si l'écran d'accueil n'est pas compatible avec une mise en page basée sur une grille.
  • Les attributs minWidth et minHeight spécifient la taille par défaut du widget en dp. Si les valeurs de largeur ou de hauteur minimales d'un widget ne correspondent pas aux dimensions des cellules, elles sont arrondies à la taille de cellule la plus proche.
Nous vous recommandons de spécifier les deux ensembles d'attributs (targetCellWidth et targetCellHeight, ainsi que minWidth et minHeight) afin que votre application puisse utiliser minWidth et minHeight si l'appareil de l'utilisateur n'est pas compatible avec targetCellWidth et targetCellHeight. S'ils sont acceptés, les attributs targetCellWidth et targetCellHeight sont prioritaires sur les attributs minWidth et minHeight.
minResizeWidth et minResizeHeight Spécifiez la taille minimale absolue du widget. Ces valeurs spécifient la taille en dessous de laquelle le widget est illisible ou inutilisable. L'utilisation de ces attributs permet à l'utilisateur de redimensionner le widget à une taille inférieure à la taille par défaut. L'attribut minResizeWidth est ignoré s'il est supérieur à minWidth ou si le redimensionnement horizontal n'est pas activé. Consultez resizeMode. De même, l'attribut minResizeHeight est ignoré s'il est supérieur à minHeight ou si le redimensionnement vertical n'est pas activé.
maxResizeWidth et maxResizeHeight Spécifiez la taille maximale recommandée du widget. Si les valeurs ne sont pas un multiple des dimensions de la cellule de la grille, elles sont arrondies à la taille de cellule la plus proche. L'attribut maxResizeWidth est ignoré s'il est inférieur à minWidth ou si le redimensionnement horizontal n'est pas activé. Consultez resizeMode. De même, l'attribut maxResizeHeight est ignoré s'il est inférieur à minHeight ou si le redimensionnement vertical n'est pas activé. Introduit dans Android 12.
resizeMode Spécifie les règles de redimensionnement d'un widget. Vous pouvez utiliser cet attribut pour redimensionner les widgets de l'écran d'accueil horizontalement, verticalement ou sur les deux axes. Les utilisateurs appuient de manière prolongée sur un widget pour afficher ses poignées de redimensionnement, puis font glisser les poignées horizontales ou verticales pour modifier sa taille sur la grille de mise en page. Les valeurs de l'attribut resizeMode incluent horizontal, vertical et none. Pour déclarer un widget comme redimensionnable horizontalement et verticalement, utilisez horizontal|vertical.

Exemple

Pour illustrer l'impact des attributs du tableau précédent sur le dimensionnement des widgets, supposons les spécifications suivantes :

  • Une cellule de la grille mesure 30 dp de large et 50 dp de haut.
  • La spécification d'attribut suivante est fournie :
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

À partir d'Android 12 :

Utilisez les attributs targetCellWidth et targetCellHeight comme taille par défaut du widget.

Par défaut, la taille du widget est de 2x2. Le widget peut être redimensionné jusqu'à 2x1 ou 4x3.

Android 11 et versions antérieures :

Utilisez les attributs minWidth et minHeight pour calculer la taille par défaut du widget.

La largeur par défaut est Math.ceil(80 / 30) = 3.

La hauteur par défaut est Math.ceil(80 / 50) = 2.

Par défaut, la taille du widget est de 3x2. Le widget peut être redimensionné jusqu'à 2x1 ou en plein écran.

Attributs de widget supplémentaires

Le tableau suivant décrit les attributs <appwidget-provider> relatifs à des qualités autres que la taille du widget.

Attributs et description
updatePeriodMillis Définit la fréquence à laquelle le framework de widget demande une mise à jour à GlanceAppWidgetReceiver en appelant la méthode de rappel onUpdate(). Nous vous recommandons de mettre à jour le moins souvent possible (pas plus d'une fois par heure) pour économiser la batterie. Pour en savoir plus, consultez la section Quand mettre à jour les widgets dans la gestion de l'état de Glance.
initialLayout Pointe vers la ressource de mise en page qui définit la mise en page de chargement du widget avant l'affichage des compositions de l'UI Glance. Vous pouvez utiliser la mise en page de chargement prédéfinie fournie dans la bibliothèque : @layout/glance_default_loading_layout.
configure Définit l'activité de configuration qui se lance lorsque l'utilisateur ajoute le widget. Consultez le guide Autoriser les utilisateurs à configurer des widgets d'application.
description Spécifie la description que le sélecteur de widgets doit afficher pour votre widget. Introduit dans Android 12.
previewLayout (Android 12) et previewImage (Android 11 et versions antérieures)
  • À partir d'Android 12, l'attribut previewLayout spécifie un aperçu évolutif, que vous fournissez sous la forme d'une mise en page XML définie sur la taille par défaut du widget. Idéalement, il pointe vers un mappage XML statique correspondant à votre mise en page.
  • Sous Android 11 ou version antérieure, l'attribut previewImage spécifie une capture d'écran d'image drawable statique de l'apparence du widget, qui s'affiche dans le sélecteur de widgets.
Nous vous recommandons de spécifier les deux afin que votre application puisse s'adapter correctement aux anciennes plates-formes. Pour les plates-formes plus récentes (Android 15 ou version ultérieure), vous pouvez définir des aperçus générés en direct en Kotlin à l'aide de "GlanceAppWidget.providePreview". Consultez le guide sur les aperçus générés.
autoAdvanceViewId Spécifie l'ID de la sous-vue du widget qui est avancée automatiquement par l'hôte du widget.
widgetCategory Indique si votre widget peut être affiché sur l'écran d'accueil (home_screen), l'écran de verrouillage (keyguard) ou les deux. Pour Android 5.0 ou version ultérieure, seul home_screen est valide.
widgetFeatures Déclare les fonctionnalités prises en charge par le widget. Par exemple, si la configuration de votre widget est facultative, spécifiez à la fois configuration_optional et reconfigurable.

Définir GlanceAppWidget

  1. Créez une classe qui étend GlanceAppWidget et remplace la méthode provideGlance. Cette méthode vous permet de charger les données nécessaires à l'affichage de votre widget :

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. Instanciez-le dans le glanceAppWidget sur votre GlanceAppWidgetReceiver :

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

Vous avez maintenant configuré un AppWidget à l'aide de Glance.

Gérer la mémoire des images des widgets dans les buckets de taille

Lorsque votre widget affiche des bitmaps ou des URI d'image content://, maintenez la mémoire totale de l'image RemoteViews sérialisée dans la limite du système (6 * width * height octets, appliquée avec un IllegalArgumentException sur Android 17 et versions ultérieures) en procédant comme suit :

  • Décoder les bitmaps partagés avant provideContent : si votre widget utilise SizeMode.Responsive ou SizeMode.Exact, Glance exécute le lambda provideContent une fois pour chaque bucket DpSize. Le décodage d'un Bitmap dans provideContent alloue une instance Bitmap distincte pour chaque bucket, ce qui multiplie l'utilisation de la mémoire dans RemoteViews.mBitmapCache. Décodez les bitmaps sous-échantillonnés (Bitmap.Config.RGB_565 pour les images opaques) une fois dans provideGlance avant provideContent afin que tous les buckets partagent une seule référence Bitmap.
  • Utilisez l'importation ImageProvider correspondante : Jetpack Glance fournit deux surcharges ImageProvider distinctes dans deux packages :

    • Utilisez import androidx.glance.ImageProvider lorsque vous transmettez une ressource ImageProvider(bitmap: Bitmap) en mémoire ou une ressource drawable ImageProvider(resId: Int).
    • Utilisez import androidx.glance.appwidget.ImageProvider lorsque vous transmettez un URI ImageProvider(uri: Uri). Lorsque vous diffusez des fichiers mis en cache via un URI FileProvider, accordez l'autorisation Intent.FLAG_GRANT_READ_URI_PERMISSION à chaque package de lanceur d'applications Intent.CATEGORY_HOME installé afin que l'hôte puisse afficher l'image.

Utiliser la classe GlanceAppWidgetReceiver pour gérer les diffusions de widgets

Le widget GlanceAppWidgetReceiver coordonne les diffusions et les mises à jour de l'état de la plate-forme en étendant le AppWidgetProvider sous-jacent. Il reçoit des événements de plate-forme lorsque votre widget est mis à jour, supprimé, activé ou désactivé, et les traduit en requêtes de cycle de vie Compose.

Déclarer un widget dans le fichier manifeste

Déclarez votre sous-classe de classe GlanceAppWidgetReceiver en tant que broadcast receiver dans votre fichier AndroidManifest.xml :

<receiver android:name="MyReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/my_app_widget_info" />
</receiver>

L'élément <receiver> nécessite l'attribut android:name, qui spécifie la classe du récepteur. Le récepteur doit accepter l'action de diffusion ACTION_APPWIDGET_UPDATE dans <intent-filter>.

L'élément <meta-data> doit identifier son nom en tant que android.appwidget.provider, et l'attribut android:resource doit pointer vers votre ressource de métadonnées XML AppWidgetProviderInfo (@xml/my_app_widget_info).

Implémenter la classe GlanceAppWidgetReceiver

Dans Glance, vous étendez GlanceAppWidgetReceiver au lieu de AppWidgetProvider directement. Implémentez-le en associant votre récepteur à votre instance GlanceAppWidget. Les principaux rappels disponibles dans GlanceAppWidgetReceiver fonctionnent comme suit :

  • onUpdate() : automatiquement remplacé par Glance pour exécuter les mises à jour de composition. Si vous remplacez manuellement onUpdate, vous devez appeler super.onUpdate pour permettre à Glance de lancer correctement les threads de composition.
  • onAppWidgetOptionsChanged() : appelée lorsque le widget est placé ou redimensionné pour la première fois. Glance lit les éléments du bundle d'options en arrière-plan afin que votre mise en page s'ajuste de manière fluide en fonction des dimensions d'exécution.
  • onDeleted(Context, IntArray) : appelée chaque fois qu'une instance de widget spécifique est supprimée par l'utilisateur.
  • onEnabled(Context) : déclenché lorsque la première instance de votre widget est créée. Excellent pour exécuter des migrations globales.
  • onDisabled(Context) : appelé lorsque la dernière instance active du fournisseur est supprimée.
  • onReceive(Context, Intent) : intercepte chaque diffusion de la plate-forme avant des méthodes de rappel spécifiques. Vous devez vous assurer que toute logique de récepteur personnalisée que vous écrivez appelle super.onReceive(context, intent) et ne doit jamais appeler goAsync vous-même, car Glance achemine automatiquement le travail de manière asynchrone.

Recevoir les intents de diffusion du widget

En arrière-plan, GlanceAppWidgetReceiver filtre et gère les intents de diffusion de widget de plate-forme fondamentaux suivants :

Créer une UI

L'extrait de code suivant montre comment créer l'UI :

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

L'exemple de code précédent effectue les opérations suivantes :

  • Dans le Column de niveau supérieur, les éléments sont placés verticalement les uns après les autres.
  • Le Column s'agrandit pour correspondre à l'espace disponible (via GlanceModifier), aligne son contenu en haut (verticalAlignment) et le centre horizontalement (horizontalAlignment).
  • Le contenu de Column est défini à l'aide de l'expression lambda. L'ordre est important.
    • Le premier élément de Column est un composant Text avec une marge intérieure de 12.dp.
    • Le deuxième élément est un Row, où les éléments sont placés horizontalement les uns après les autres, avec deux Buttons centrés horizontalement (horizontalAlignment). L'affichage final dépend de l'espace disponible. L'image suivante en est un exemple :
destination_widget
Figure 1. Exemple d'interface utilisateur.

Vous pouvez modifier les valeurs d'alignement ou appliquer différentes valeurs de modificateur (comme la marge intérieure) pour modifier l'emplacement et la taille des composants. Consultez la documentation de référence pour obtenir la liste complète des composants, des paramètres et des modificateurs disponibles pour chaque classe.

Implémenter des angles arrondis

Android 12 introduit des paramètres système permettant de personnaliser de manière dynamique les rayons d'angle des widgets de votre application :

  • system_app_widget_background_radius : spécifie le rayon d'angle du conteneur d'arrière-plan du widget (jamais supérieur à 28 dp).
  • Rayon intérieur : pour éviter que le contenu ne soit tronqué, calculez un rayon proportionnel pour votre contenu intérieur en fonction du contour de l'arrière-plan du système : systemRadiusValue - widgetPadding

Dans Glance, vous pouvez appliquer des propriétés de dimensionnement du rayon d'angle de manière dynamique dans la composition à l'aide de GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

Pour assurer la rétrocompatibilité sur les appareils équipés d'Android 11 (niveau d'API 30) ou d'une version antérieure, implémentez des attributs personnalisés et des solutions de remplacement pour les ressources de thème personnalisées :

  • /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>