Tuto sur App Widgets pour Android : Partie 1 : créer un widget

Publié le 03 juillet 2018 par Rhw @RevueHW

Les applications les plus réussies sont souvent les plus simples à utiliser. Cela signifie que les utilisateurs peuvent voir les informations dont ils ont besoin « en un coup d’œil » sans déverrouiller leur téléphone ou lancer l’application associée. Sur la plate-forme Android, vous pouvez y parvenir par deux manières différentes. La première, et la plus récente, est Android Wear. La seconde, le sujet de ce tutoriel, est la mise en œuvre de App Widgets. Ces derniers sont disponibles dans l’écosystème Android depuis la version Android 1.6 (Donut).

Dans cet article, vous allez créer un App Widget pour une application Coffee Log qui vous permettra de contrôler votre consommation quotidienne de caféine directement à partir de votre écran d’accueil.

Remarque : La plupart des développeurs aiment le café, mais nous savons aussi que la santé est très importante, donc on vous conseille de lire cet article intéressant.

Vous suivrez le processus typique de développement de widgets et apprendrez comment créer l’interface utilisateur Widget.

Pour ce tutoriel, vous aurez également besoin d’Android Studio 3.1.2 ou ultérieur.

Commençons !

La première chose que vous devriez faire est de télécharger l’exemple de projet pour ce tutoriel. Le fichier zip contient les projets Android Studio pour les versions initiale et finale de l’application Coffee Log.

Décompressez le fichier dans un dossier de votre choix, allez dans Fichier/Ouvrir ou choisissez « Ouvrir un projet Android Studio existant » dans la fenêtre Bienvenue dans Android Studio, et sélectionnez le fichier build.gradle dans le dossier racine du projet de démarrage.

Une fois que le projet a terminé de charger et d’exécuter un build Gradle, vous pouvez jeter un œil à la structure du fichier, qui devrait ressembler à ceci :

Maintenant que vous êtes dans le projet, jetez un coup d’œil, surtout dans MainActivity, où toute la journalisation se produit. CoffeeTypes est une classe enum simple avec tous les types de café et leur quantité de caféine en grammes, tandis que la classe CoffeeLoggerPersistence gère la persistance à l’aide de SharedPreferences.

Il est temps de commencer à suivre notre consommation de caféine ! Créez et exécutez l’application en accédant au Build\Make Project ou en utilisant le bouton vert « play » de la barre d’outils. L’application apparaîtra dans votre émulateur ou appareil, et ressemble à ceci :

L’application vous permet de voir combien de grammes de café vous avez bu jusqu’à présent et de sélectionner de nouvelles boissons pour mettre à jour votre consommation. Chaque sélection entraîne une mise à jour du total affiché.

Pour utiliser l’application afin de contrôler votre consommation de café, vous devez lancer l’application complète. Comme toujours, nous pouvons faire mieux. Que diriez-vous de rendre la vie de votre utilisateur plus simple avec un App Widget comme celui-ci ?

Avec un widget, vous pouvez accéder aux mêmes informations que l’application, et afficher un puissant quote de motivation, à partir de l’écran d’accueil de votre appareil. Comme vous pouvez le voir, la disposition est différente parce que la liste est maintenant un ensemble de 3 boutons.

Il y a beaucoup de choses à maîtriser pour créer un App Widegt, alors allons-y !

Anatomie de App Widget

Comme le dit la documentation Android, un App Widget est un composant qui peut être intégré dans d’autres applications, généralement à l’écran d’accueil. La sécurité et la performance sont très importantes, la plateforme Android a défini un protocole très clair qui décrit comment un App Widget communique avec sa propre application et interagit avec celui qui l’héberge. C’est pourquoi le développeur doit fournir un fichier de configuration avec les informations suivantes :

  • La disposition du widget
  • L’espace de l’écran Widget
  • Est-ce que le widget est redimensionnable et comment le faire ?
  • Une image d’aperçu que les utilisateurs verront lorsqu’ils font glisser le widget sur l’écran
  • À quelle fréquence les données peuvent-elles être rafraîchies ?
  • Un écran de configuration facultatif

Comme vous le verrez, le système Android utilise ces informations à différentes étapes du cycle de vie des widgets. Les informations sur la mise en page ou layout sont utiles lorsque le widget fonctionne et interagit avec l’utilisateur. Le redimensionnement, l’aperçu et l’espace de l’écran requis sont utiles lorsque l’utilisateur décide de sélectionner le widget et de le faire glisser dans l’écran d’accueil.

L’interface utilisateur

Comme vous l’avez vu dans les images précédentes, les applications et les widgets ont des interfaces utilisateur différentes. C’est parce que l’espace disponible est différent, ainsi que les modes d’interaction de l’utilisateur. Pour les applications et les widgets, vous pouvez définir le layout à l’aide d’un fichier de ressources.

Vous devez vous rappeler qu’un Widget fonctionne dans une application différente et que certaines restrictions sont mises en place pour des raisons de sécurité et de performance. Cela signifie que vous ne pouvez utiliser qu’un sous-ensemble des composants standard, avec lequel vous pouvez interagir uniquement avec un objet spécifique de type RemoteViews. En particulier, vous pouvez utiliser uniquement :

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

Avec ViewStub, qui permet un gonflement paresseux d’un layout, vous ne pouvez utiliser que les conteneurs suivants :

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

Les extensions de ces classes ne sont pas autorisées.

Le contrôle de ces contraintes est fort. En raison de ces restrictions, un layout Widget doit être très simple et utiliser uniquement des composants simples comme TextView, Button ou ImageView.

Redimensionnement et aperçu

Le fichier de configuration est le mécanisme utilisé pour décrire votre Widget sur le système Android. Vous pouvez l’utiliser pour définir les tailles de widget prises en charge, indiquer au système si le widget est redimensionnable ou non et fournir une image à afficher lorsque l’utilisateur décide d’ajouter un widget à son écran d’accueil. Vous verrez tout cela lorsque vous insérerez votre widget pour la première fois.

Actualiser le widget

Les données affichées par le widget doivent toujours être à jour sans gaspiller les ressources système. Cela signifie que l’interface utilisateur doit être mise à jour uniquement lorsque les données changent, ce qui peut se produire pour différentes raisons. Si l’utilisateur interagit avec le widget, vous avez besoin d’un moyen de mettre à jour l’interface utilisateur, puis d’envoyer l’événement à l’application principale. Si quelque chose se passe dans l’application principale, vous avez besoin d’un moyen de dire au Widget d’actualiser.

La plate-forme Android fournit également une troisième manière, une actualisation automatique du widget à un intervalle qui peut être défini à l’aide du fichier de configuration. Les limitations de performances ne permettent pas une fréquence de mise à jour supérieure à 30 minutes.

Personnalisation de Widget

Dans le cas de Coffee Log, il n’y a que trois différents types de cafés. Mais, que se passe-t-il si l’utilisateur n’est pas intéressé par un Café long ou qu’ils veulent juste un autre verre, ou qu’ils veulent simplement changer la quantité de grammes. Ou peut-être que l’utilisateur veut personnaliser la couleur d’arrière-plan du widget. Comme vous le verrez, il est possible de fournir un écran de configuration pour permettre toute la personnalisation nécessaire.

Créez votre widget

Assez de théorie, maintenant vous pouvez commencer à créer votre widget. La création d’un widget nécessite la définition de certains fichiers de code et de configuration en fonction de la spécification définie par la plateforme Android.

Android Studio rend ce processus très facile, grâce à l’utilisation d’un simple assistant, auquel vous pouvez accéder en sélectionnant Nouveau\Widget\App Widget dans le menu Fichier. Vous verrez la fenêtre suivante :

Ajoutez l’entrée suivante à la fenêtre :

  • Nom de la classe : CoffeeLoggerWidget
  • Largeur minimale (cellules) : 3
  • Hauteur minimum (cellules) : 2

Là, vous pouvez également voir qu’il est possible de définir si le Widget est redimensionnable et quelles sont ses destinations possibles. Un widget fait généralement partie de l’écran d’accueil, mais il peut également faire partie du verrouillage du clavier (Keyguard), qui est l’écran qui apparaît lorsque le téléphone est verrouillé.

Sélectionnez Terminer, et Android Studio créera trois fichiers pour vous :

  • CoffeeLoggerWidget.kt : ceci est une classe Kotlin avec le même nom utilisé dans l’assistant, et agit comme un contrôleur pour le Widget. Vous apprendrez comment modifier ce code afin d’accéder au composant de l’interface utilisateur via la classe RemoteViews, et comment recevoir et gérer les événements à partir du widget lui-même.
  • coffee_logger_widget_info.xml : c’est le fichier de configuration que nous avons décrit précédemment avec des informations sur le taux d’actualisation, le redimensionnement, les dimensions, etc. C’est le fichier que vous allez éditer afin de fournir une configuration pour le widget.
  • coffee_logger_widget.xml : ce fichier contient la disposition de l’interface utilisateur du widget.

Il est important de noter où se trouvent tous ces fichiers dans la structure du projet :

Particulièrement, vous voyez comment le fichier de configuration a été créé en tant que fichier de ressources XML.

Comme vous le verrez plus tard, l’assistant a également apporté quelques modifications au fichier AndroidManifest.xml de l’application.

Personnalisez l’interface utilisateur

Pour personnaliser l’interface utilisateur du widget, ouvrez le fichier coffee_logger_widget.xml dans le dossier app\res\layout. L’assistant Android Studio a généré la disposition suivante que vous devez mettre à jour :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#09C"
  android:padding="@dimen/widget_margin">

  <TextView
    android:id="@+id/appwidget_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:layout_margin="8dp"
    android:background="#09C"
    android:contentDescription="@string/appwidget_text"
    android:text="@string/appwidget_text"
    android:textColor="#ffffff"
    android:textSize="24sp"
    android:textStyle="bold|italic" />

</RelativeLayout>

Supprimez le TextView et remplacez le RelativeLayout par un LinearLayout. Dans Android Studio, vous pouvez le faire en double-cliquant sur l’ancien nom et en tapant sur le nouveau nom. Après ce changement, vous devriez avoir ceci :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#09C"
  android:padding="@dimen/widget_margin">
</LinearLayout>

Remarque : vous allez utiliser des styles déjà définis dans le projet exemple. Ils contiennent des tailles et des couleurs de texte, des hauteurs, des largeurs, des alignements et d’autres styles. Si vous êtes curieux à leur sujet, consultez styles.xml dans le dossier res/values

Ensuite, ajoutez trois attributs supplémentaires à LinearLayout :

  ...
  android:id="@+id/widget_layout"
  android:orientation="vertical"
  android:gravity="center"
  ...

Les attributs android:orientation et android:gravity donnent à LinearLayout des informations sur la façon d’aligner son contenu. Fournir un identifiant est également important dans le cas où nous devons obtenir une référence à la mise en page dans le code Kotlin.

Pour obtenir des angles arrondis, remplacez l’attribut android:background par @drawable/background, un dessin disponible dans le projet de démarrage. Maintenant, l’élément racine de layout ressemble à ceci :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/widget_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@drawable/background"
  android:gravity="center"
  android:orientation="vertical"
  android:padding="@dimen/widget_margin">
</LinearLayout>

Pensez verticalement

Pour des raisons esthétiques, l’interface utilisateur devrait être belle quelle que soit la taille du widget. Il est préférable d’avoir les éléments Widget répartis sur l’espace disponible. Il y a plusieurs façons de réaliser cela, mais vous devriez utiliser la manière la plus simple qui consiste à ajouter des composants TextView qui vont se développer dans l’espace restant entre les autres éléments.

Voici un schéma de la mise en page que vous allez créer :

Le motif vert sera un TextView qui s’étend verticalement et le motif bleu sera un TextView qui s’étend horizontalement. Gardez ce schéma à l’esprit lorsque vous construisez la mise en page pour comprendre pourquoi vous ajoutez chaque élément.

Remarque : Si vous êtes tenté de remplir les espaces vides à l’aide d’un Espace au lieu de TextView, rappelez-vous qu’un widget a des restrictions d’interface utilisateur et qu’un espace n’est pas un des composants autorisés.

Le premier élément de LinearLayout est un espace vertical que vous pouvez définir en ajoutant ce code en tant que premier enfant :

<TextView style="@style/WidgetButtonVerticalSpace" />

Maintenant, vous pouvez ajouter les composants TextView pour la quantité de café :

  <TextView
    android:id="@+id/appwidget_text"
    style="@style/WidgetTextView.Big" />

  <TextView
    style="@style/WidgetTextView"
    android:text="@string/grams" />

Puis, ajoutez un autre TextView pour l’espace vertical suivant avant les boutons :

<TextView style="@style/WidgetButtonVerticalSpace" />

Notez que le premier affichage du texte doit avoir un id parce que vous devrez changer le texte plus tard à partir du code de Kotlin. Le second est un texte fixe. Vous utilisez les styles prédéfinis dans les affichages de texte.

Ensuite, ajoutez un conteneur pour les boutons en tant que LinearLayout avec une orientation horizontale :

<LinearLayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal">

    <!-- Buttons go here -->

</LinearLayout>

Puis, un TextView pour le quote après le dernier espace vertical.

<TextView style="@style/WidgetButtonVerticalSpace" />

<TextView
   android:id="@+id/coffee_quote"
   style="@style/WidgetQuote" />

Ajoutez des boutons

Maintenant, la partie verte de la mise en page est terminée et vous devez gérer la partie bleue pour les boutons suivant ce schéma :

Vous avez déjà créé un conteneur pour eux, il vous suffit donc de commencer avec un TextView qui s’étend horizontalement et qui gardera le premier bouton à distance de la marge de gauche :

<TextView style="@style/WidgetButtonHorizontalSpace" />

Ensuite, vous pouvez ajouter le premier bouton pour le café le plus court du monde :

<LinearLayout
  android:id="@+id/ristretto_button"
  style="@style/WidgetBeverageButton" >

  <ImageView
    style="@style/WidgetButtonImage"
    android:src="@drawable/ic_ristretto" />
  <TextView
    style="@style/WidgetButtonText"
    android:text="@string/ristretto_short" />

</LinearLayout>

<TextView style="@style/WidgetButtonHorizontalSpace" />

Chaque bouton a un LinearLayout qui contient un ImageView et un TextView. Après le bouton, vous ajoutez un autre TextView qui s’étend horizontalement pour faciliter la propagation des boutons.

Ajoutez le bouton suivant pour Espresso :

<LinearLayout
  android:id="@+id/espresso_button"
  style="@style/WidgetBeverageButton">

  <ImageView
    style="@style/WidgetButtonImage"
    android:src="@drawable/ic_espresso" />
  <TextView
    style="@style/WidgetButtonText"
    android:text="@string/espresso_short" />

</LinearLayout>

<TextView style="@style/WidgetButtonHorizontalSpace" />

Et le dernier bouton, c’est pour le café Long :

<LinearLayout
  android:id="@+id/long_button"
  style="@style/WidgetBeverageButton" >

  <ImageView
    style="@style/WidgetButtonImage"
    android:src="@drawable/ic_long_coffee" />
  <TextView
    style="@style/WidgetButtonText"
    android:text="@string/long_coffee_short" />

</LinearLayout>

<TextView style="@style/WidgetButtonHorizontalSpace" />

Ouf ! C’était long, mais vous avez terminé avec la mise en page pour le widget.

Exécutez votre widget

Le widget que vous avez créé est parfait, mais il ne fait rien pour le moment. Créez et exécutez votre application pour vous assurer qu’il n’y a pas d’erreur dans le fichier XML. Juste pour être sûr que tout va bien, ajoutez le widget à l’écran. Si vous n’avez jamais ajouté un widget à votre écran d’accueil, voici les étapes à suivre :

  1. Allez à l’écran d’accueil
  2. Appuyez longuement sur un espace vide
  3. Sélectionnez « Widgets »
  4. Appuyez longuement sur le Widget Coffee Log
  5. Déposez-le où vous voulez sur l’écran

Votre widget ressemble à ceci :

Remarquez comment le code autogénéré a composé le premier TextView avec « EXAMPLE ».

Effectuez des actions

Il est maintenant temps d’ajouter de l’interactivité au widget. Lorsque l’utilisateur sélectionne un bouton, vous devez ouvrir MainActivity, en transmettant des informations sur le café sélectionné afin de mettre à jour le nombre total de grammes dans l’enregistrement d’aujourd’hui.

Malheureusement, lancer un simple Intent ne suffit pas, car nous devons nous rappeler que notre Widget fonctionne dans une application différente de la nôtre et fonctionne dans un autre processus Android. Pour cela, la plate-forme Android a une solution, PendingIntent, qui est essentiellement un moyen de demander à une autre application de lancer un Intent.

Ouvrez ensuite le fichier CoffeeLoggerWidget.kt et ajoutez cette fonction utilitaire à la fin de l’objet compagnon :

private fun getPendingIntent(context: Context, value: Int): PendingIntent {
  //1
  val intent = Intent(context, MainActivity::class.java)
  //2
  intent.action = Constants.ADD_COFFEE_INTENT
  //3
  intent.putExtra(Constants.GRAMS_EXTRA, value)
  //4
  return PendingIntent.getActivity(context, value, intent, 0)
}

Cette fonction de Kotlin a la responsabilité de créer un PendingIntent pour un café donné :

  1. D’abord, vous définissez l’Intent qui lance comme d’habitude en utilisant la classe de destination comme argument ; dans votre cas, c’est la classe MainActivity.
  2. MainActivity peut être lancé de différentes manières, et vous avez besoin d’identifier à quelle fréquence varier la teneur en café. Pour ce faire, vous utilisez une action que MainActivity peut reconnaître.
  3. Vous devez également mettre dans l’Intent la quantité à ajouter. Rappelez-vous que MainActivity ne sait pas quel bouton a été pressé sur le Widget !
  4. Créez le PendingIntent et renvoyez-le à l’appelant de la fonction

Puisque vous avez maintenant préparé les actions, attachez-les aux boutons. Accédez à la fonction updateAppWidget () dans l’objet compagnon (companion object) et ajoutez le code suivant juste avant sa dernière instruction appWidgetManager.updateAppWidget (…) :

views.setOnClickPendingIntent(R.id.ristretto_button, 
    getPendingIntent(context, CoffeeTypes.RISTRETTO.grams))
views.setOnClickPendingIntent(R.id.espresso_button, 
    getPendingIntent(context, CoffeeTypes.ESPRESSO.grams))
views.setOnClickPendingIntent(R.id.long_button, 
    getPendingIntent(context, CoffeeTypes.LONG.grams))

Il convient de noter que updateAppWidget () est une méthode pratique créée par l’assistant Android Studio pour encapsuler la logique de mise à jour d’un widget donné. En regardant la même classe Kotlin, vous voyez qu’elle est invoquée dans la méthode onUpdate () pour chaque Widget nécessitant une mise à jour. Cet appel se produit également lorsque le widget apparaît dans l’application d’hébergement pour la première fois.

override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
  // There may be multiple widgets active, so update all of them
  for (appWidgetId in appWidgetIds) {
    updateAppWidget(context, appWidgetManager, appWidgetId)
  }
}

La classe RemoteViews

Maintenant, votre code devrait ressembler à ceci :

internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager,
                                 appWidgetId: Int) {
  //1
  val widgetText = context.getString(R.string.appwidget_text)
  //2
  val views = RemoteViews(context.packageName, R.layout.coffee_logger_widget)
  //3
  views.setTextViewText(R.id.appwidget_text, widgetText)
  //4
  views.setOnClickPendingIntent(R.id.ristretto_button, 
      getPendingIntent(context, CoffeeTypes.RISTRETTO.grams))
  views.setOnClickPendingIntent(R.id.espresso_button, 
      getPendingIntent(context, CoffeeTypes.ESPRESSO.grams))
  views.setOnClickPendingIntent(R.id.long_button, 
      getPendingIntent(context, CoffeeTypes.LONG.grams))
  // 5
  appWidgetManager.updateAppWidget(appWidgetId, views)
}

Voici ce qui se passe :

  1. Vous utilisez le Context pour accéder à une ressource de chaîne
  2. Une instance de la classe RemoteViews est créée et reçoit l’ID de disposition du widget. Un RemoteViews est essentiellement une image miroir de ce que vous allez afficher dans le widget.
  3. Vous définissez la chaîne précédente en tant que contenu de TextView avec l’id R.id.appwidget_text. Il est très important de noter que vous ne pouvez pas accéder directement au TextView et que seules certaines opérations sont autorisées à utiliser RemoteViews ; dans ce cas, vous définissez un texte.
  4. À l’aide de l’instance RemoteViews, vous enregistrez un PendingIntent à utiliser lorsque l’utilisateur clique sur un bouton Widget.
  5. La dernière instruction lie l’instance spécifique de RemoteViews à l’instance spécifique du widget.

Créez et exécutez maintenant. Vous ne verrez aucune différence dans le widget, mais cliquer sur les boutons Widget va ouvrir l’application avec une valeur mise à jour de grammes. Bon travail !