Créer un AppWidget

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Comme vous le savez probablement, une des forces d'Android est son côté personnalisable. Un des exemples les plus probants est qu'il est tout à fait possible de choisir les éléments qui se trouvent sur l'écran d'accueil. On y trouve principalement des icônes, mais les utilisateurs d'Android sont aussi friands de ce qu'on appelle les « AppWidgets », applications miniatures destinées à être utilisées dans d'autres applications. Ce AppWidgets permettent d'améliorer une application à peu de frais en lui ajoutant un compagnon permanent. De plus, mettre un AppWidget sur son écran d'accueil permet à l'utilisateur de se rappeler l'existence de votre produit et par conséquent d'y accéder plus régulièrement. Par ailleurs, les AppWidgets peuvent accorder un accès direct à certaines fonctionnalités de l’application sans avoir à l'ouvrir, ou même ouvrir l'application ou des portions de l'application.

Un AppWidget est divisé en plusieurs unités, toutes nécessaires pour fonctionner. On retrouve tout d'abord une interface graphique qui détermine quelles sont les vues qui le composent et leurs dispositions. Ensuite, un élément gère le cycle de vie de l'AppWidget et fait le lien entre l'AppWidget et le système. Enfin, un dernier élément est utilisé pour indiquer les différentes informations de configuration qui déterminent certains aspects du comportement de l'AppWidget. Nous allons voir tous ces éléments, comment les créer et les manipuler.

L'interface graphique

La première chose à faire est de penser à l'interface graphique qui représentera la mise en page de l'AppWidget. Avant de vous y mettre, n'oubliez pas de réfléchir un peu. Si votre AppWidget est l'extension d'une application, faites en sorte de respecter la même charte graphique de manière à assurer une véritable continuité dans l'utilisation des deux programmes. Le pire serait qu'un utilisateur ne reconnaisse pas votre application en voyant un AppWidget et n'arrive pas à associer les deux dans sa tête.

Vous allez comme d'habitude devoir créer un layout dans le répertoire res/layout/. Cependant, il ne peut pas contenir toutes les vues qui existent. Voici les layouts acceptés :

  • FrameLayout
  • LinearLayout
  • RelativeLayout

… ainsi que les widgets acceptés :

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView

Ne confondez pas les widgets, ces vues qui ne peuvent pas contenir d'autres vues, et les AppWidgets. Pour être franc, vous trouverez le terme « widget » utilisé pour désigner des AppWidgets, ce qui est tout à fait correct, mais pour des raisons pédagogiques je vais utiliser AppWidget.

Pourquoi uniquement ces vues-là ?

Toutes les vues ne sont pas égales. Ces vues-là sont des RemoteViews, c'est-à-dire qu'on peut y avoir accès quand elles se trouvent dans un autre processus que celui dans lequel on travaille. Au lieu de désérialiser une hiérarchie de vues comme on le fait d'habitude, on désérialisera un layout dans un objet de type RemoteViews. Il est ainsi possible de configurer les vues dans notre receiver pour les rendre accessibles à une autre activité, celle de l'écran d'accueil par exemple.

L'une des contreparties de cette technique est que vous ne pouvez pas implémenter facilement la gestion des évènements avec un OnClickListener par exemple. À la place, on va attribuer un PendingIntent à notre RemoteViews de façon à ce qu'il sache ce qu'il doit faire en cas de clic, mais nous le verrons plus en détail bientôt.

Enfin, sachez qu'on ne retient pas de référence à des RemoteViews, tout comme on essaie de ne jamais faire de référence à des context.

Voici un exemple standard :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:gravity="center"
  android:orientation="vertical"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/background" >
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/title"
    android:textColor="#ff00ff00"
    android:text="@string/title"
    android:background="@drawable/black"
    android:gravity="center" />
  <Button
    android:id="@+id/bouton"
    android:layout_width="146dip"
    android:layout_height="72dip"
    android:text="@string/bouton"
    android:background="@drawable/black"
    android:gravity="center" />
</LinearLayout>

Vous remarquerez que j'ai utilisé des valeurs bien précises pour le bouton. En effet, il faut savoir que l'écran d'accueil est divisé en cellules. Une cellule est l'unité de base de longueur dans cette application, par exemple une icône fait une cellule de hauteur et une cellule de largeur. La plupart des écrans possèdent quatre cellules en hauteur et quatre cellules en largeur, ce qui donne $4 \times 4 = 16$ cellules en tout.

Pour déterminer la mesure que vous désirez en cellules, il suffit de faire le calcul $(74 \times N) - 2$ avec N le nombre de cellules voulues. Ainsi, j'ai voulu que mon bouton fasse une cellule de hauteur, ce qui donne $(74 \times 1) - 2 = 72$ dp.

Vous vous rappelez encore les dp ? C'est une unité qui est proportionnelle à la résolution de l'écran, contrairement à d'autres unités comme le pixel par exemple. Imaginez, sur un écran qui a 300 pixels en longueur, une ligne qui fait 150 pixels prendra la moitié de l'écran, mais sur un écran qui fait 1500 pixels de longueur elle n'en fera qu'un dixième ! En revanche, avec les dp (ou dip, c'est pareil), Android calculera automatiquement la valeur en pixels pour adapter la taille de la ligne à la résolution de l'écran.

Définir les propriétés

Maintenant, il faut préciser différents paramètres de l'AppWidget dans un fichier XML. Ce fichier XML représente un objet de type AppWidgetProviderInfo.

Tout d'abord, la racine est de type <appwidget-provider> et doit définir l'espace de nommage android, comme ceci :

1
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" />

Vous pouvez définir la hauteur minimale de l'AppWidget avec android:minHeight et sa largeur minimale avec android:minWidth. Les valeurs à indiquer sont en dp comme pour le layout.

Ensuite, on utilise android:updatePeriodMillis pour définir la fréquence de mise à jour voulue, en millisecondes. Ainsi, android:updatePeriodMillis="60000" fait une minute, android:updatePeriodMillis="3600000" fait une heure, etc. Puis on utilise android:initialLayout pour indiquer la référence au fichier XML qui indique le layout de l'AppWidget. Enfin, vous pouvez associer une activité qui permettra de configurer l'AppWidget avec android:configure.

Voici par exemple ce qu'on peut trouver dans un fichier du genre res/xml/appwidget_info.xml :

1
2
3
4
5
6
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" 
  android:minHeight="220dp"
  android:minWidth="146dp"
  android:updatePeriodMillis="3600000"
  android:initialLayout="@layout/widget"
  android:configure="sdz.chapitreQuatre.WidgetExample.AppWidgetConfigure" />

Le code

Le receiver

Le composant de base qui permettra l’interaction avec l'AppWidget est AppWidgetProvider. Il permet de gérer tous les évènements autour de la vie de l'AppWidget. AppWidgetProvider est une classe qui dérive de BroadcastReceiver, elle va donc recevoir les divers broadcast intents qui sont émis et qui sont destinés à l'AppWidget. On retrouve quatre évènements pris en compte : l'activation, la mise à jour, la désactivation et la suppression. Comme d'habitude, chaque période de la vie d'un AppWidget est représentée par une méthode de callback.

La mise à jour

La méthode la plus importante est celle relative à la mise à jour, vous devrez l'implémenter chaque fois. Il s'agit de public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) avec comme paramètres :

  • Le context dans lequel le receiver s'exécute.
  • appWidgetManager représente le gestionnaire des AppWidgets, il permet d'avoir des informations sur tous les AppWidgets disponibles sur le périphérique et de les mettre à jour.
  • Les identifiants des AppWidgets à mettre à jour sont contenus dans appWidgetIds.

Cette méthode sera appelée à chaque expiration du délai updatePeriodMillis.

Ainsi, dans cette méthode, on va récupérer l'arbre de RemoteViews qui constitue l'interface graphique et on mettra à jour les vues qui ont besoin d'être mises à jour. Pour récupérer un RemoteViews, on utilisera le constructeur RemoteViews(String packageName, int layoutId) qui a besoin du nom du package du context dans packageName (on le récupère facilement avec la méthode String getPackageName() de Context) et l'identifiant du layout dans layoutId.

Vous pouvez ensuite manipuler n'importe quelle vue qui se trouve dans cette hiérarchie à l'aide de diverses méthodes de manipulation. Par exemple, pour changer le texte d'un TextView, on fera void setTextViewText(int viewId, CharSequence text) avec viewId l'identifiant du TextView et le nouveau text. Il n'existe bien entendu pas de méthodes pour toutes les méthodes que peuvent exécuter les différentes vues, c'est pourquoi RemoteViews propose des méthodes plus génériques qui permettent d'appeler des méthodes sur les vues et de leur passer des paramètres. Par exemple, un équivalent à :

1
remote.setTextViewText(R.id.textView, "Machin")

… pourrait être :

1
remote.setString(R.id.textView, "setText", "Machin")

On a en fait fait appel à la méthode setText de TextView en lui passant un String.

Maintenant que les modifications ont été faites, il faut les appliquer. En effet, elles ne sont pas effectives toutes seules, il vous faudra utiliser la méthode void updateAppWidget(int appWidgetId, RemoteViews views) avec appWidgetId l'identifiant du widget qui contient les vues et views la racine de type RemoteViews.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
  // Pour chaque instance de notre AppWidget
  for (int i = 0 ; i < appWidgetIds.length ; i++) {
    // On crée la hiérarchie sous la forme d'un RemotViews
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_widget_layout);

    // On récupère l'identifiant du widget actuel
    int id = appWidgetIds[i];
    // On met à jour toutes les vues du widget
    appWidgetManager.updateAppWidget(id, views);
  }
}

Les autres méthodes

Tout d'abord, comme AppWidgetProvider dérive de BroadcastReceiver, on pourra retrouver les méthodes de BroadcastReceiver, dont public void onReceive(Context context, Intent intent) qui est activé dès qu'on reçoit un broadcast intent.

La méthode public void onEnabled(Context context) n'est appelée que la première fois qu'un AppWidget est créé. Si l'utilisateur place deux fois un AppWidget sur l'écran d'accueil, alors cette méthode ne sera appelée que la première fois. Le broadcast intent associé est APP_WIDGET_ENABLED.

Ensuite, la méthode public void onDeleted(Context context, int[] appWidgetIds) est appelée à chaque fois qu'un AppWidget est supprimé. Il répond au broadcast intent APP_WIDGET_DELETED.

Et pour finir, quand la toute dernière instance de votre AppWidget est supprimée, le broadcast intent APP_WIDGET_DISABLED est envoyé afin de déclencher la méthode public void onDisabled(Context context).

L'activité de configuration

C'est très simple, il suffit de créer une classe qui dérive de PreferenceActivity comme vous savez déjà le faire.

Déclarer l'AppWidget dans le Manifest

Le composant de base qui représente votre application est le AppWidgetProvider, c'est donc lui qu'il faut déclarer dans le Manifest. Comme AppWidgetProvider dérive de BroadcastReceiver, il faut déclarer un nœud de type <receiver>. Cependant, contrairement à un BroadcastReceiver classique où l'on pouvait ignorer les attributs android:icon et android:label, ici il vaut mieux les déclarer. En effet, ils sont utilisés pour donner des informations sur l'écran de sélection des widgets :

1
2
3
4
5
6
<receiver 
  android:name=".AppWidgetProviderExample"
  android:label="@string/nom_de_l_application"
  android:icon="@drawable/icone"></receiver>

Il faut bien entendu rajouter des filtres à intents dans ce receiver, sinon il ne se lancera jamais. Le seul broadcast intent qui nous intéressera toujours est android.appwidget.action.APPWIDGET_UPDATE qui est envoyé à chaque fois qu'il faut mettre à jour l'AppWidget :

1
2
3
<intent-filter>
  <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>

Ensuite, pour définir l'AppWidgetProviderInfo, il faut utiliser un élément de type <meta-data> avec les attributs android:name qui vaut android.appwidget.provider et android:resource qui est une référence au fichier XML qui contient l'AppWidgetProviderInfo :

1
2
<meta-data android:name="android.appwidget.provider"
  android:resource="@xml/appwidget_info" />

Ce qui donne au complet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<receiver 
  android:name=".AppWidgetProviderExample"
  android:label="@string/nom_de_l_application"
  android:icon="@drawable/icone">
  <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
  </intent-filter>
  <meta-data android:name="android.appwidget.provider"
    android:resource="@xml/appwidget_info" />
</receiver>

Application : un AppWidget pour accéder aux tutoriels du Site du Zéro

On va créer un AppWidget qui ne sera pas lié à une application. Il permettra de choisir quel tutoriel du Site du Zéro l'utilisateur souhaite visualiser.

Résultat attendu

Mon AppWidget ressemble à la figure suivante. Évidemment, vous pouvez modifier le design pour obtenir quelque chose de plus… esthétique. Là, c'est juste pour l'exemple.

Cet AppWidget permet d'accéder à des tutoriels du Site du Zéro

On peut cliquer sur le titre du tutoriel pour lancer le tutoriel dans un navigateur. Les deux boutons permettent de naviguer dans la liste des tutoriels disponibles.

Aspect technique

Pour permettre aux trois boutons (celui qui affiche le titre est aussi un bouton) de réagir aux clics, on va utiliser la méthode void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) de RemoteViews avec viewId l'identifiant du bouton et pendingIntent le PendingIntent qui contient l'Intent qui sera exécuté en cas de clic.

Détail important : pour ajouter plusieurs évènements de ce type, il faut différencier chaque Intent en leur ajoutant un champ Données différent. Par exemple, j'ai rajouté des données de cette manière à mes intents : intent.setData(Uri.withAppendedPath(Uri.parse("WIDGET://widget/id/"), String.valueOf(Identifiant_de_cette_vue))). Ainsi, j'obtiens des données différentes pour chaque intent, même si ces données ne veulent rien dire.

Afin de faire en sorte qu'un intent lance la mise à jour de l'AppWidget, on lui mettra comme action AppWidgetManager.ACTION_APPWIDGET_UPDATE et comme extra les identifiants des widgets à mettre à jour ; l'identifiant de cet extra sera AppWidgetManager.EXTRA_APPWIDGET_ID :

1
2
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

Comme AppWidgetProvider dérive de BroadcastReceiver, vous pouvez implémenter void onReceive(Context context, Intent intent) pour gérer chaque intent qui lance ce receiver.

Ma solution

Tout d'abord je déclare mon layout :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  android:background="@drawable/background" >

  <Button android:id="@+id/link"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="30"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:layout_marginTop="10dp"
    android:background="@android:color/transparent"
    android:textColor="#FFFFFF" />

  <LinearLayout android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="70"
    android:orientation="horizontal"
    android:layout_marginBottom="10dp" >

    <Button android:id="@+id/previous"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_marginLeft="10dp"
      android:layout_weight="50"
      android:text="Prec." />

    <Button android:id="@+id/next"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_marginRight="10dp"
      android:layout_weight="50"
      android:text="Suivant" />
  </LinearLayout>

</LinearLayout>

La seule chose réellement remarquable est que le fond du premier bouton est transparent grâce à l'attribut android:background="@android:color/transparent".

Une fois mon interface graphique créée, je déclare mon AppWidgetProviderInfo :

1
2
3
4
5
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  android:minWidth="144dip"
  android:minHeight="144dip"
  android:updatePeriodMillis="3600000"
  android:initialLayout="@layout/widget" />

Je désire qu'il fasse au moins 2 cases en hauteur et 2 cases en largeur, et qu'il se rafraîchisse toutes les heures.

J'ai ensuite créé une classe très simple pour représenter les tutoriels :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package sdz.chapitrequatre.tutowidget;
import android.net.Uri;

public class Tuto {
  private String intitule = null;
  private Uri adresse = null;

  public Tuto(String intitule, String adresse) {
    this.intitule = intitule;
    this.adresse = Uri.parse(adresse);
  }

  public String getIntitule() {
    return intitule;
  }

  public void setIntitulé(String intitule) {
    this.intitule = intitule;
  }

  public Uri getAdresse() {
    return adresse;
  }

  public void setAdresse(Uri adresse) {
    this.adresse = adresse;
  }
}

Puis, le receiver associé à mon AppWidget :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package sdz.chapitrequatre.tutowidget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;

public class TutoWidget extends AppWidgetProvider {
  // Les tutos que propose notre widget
  private final static Tuto TUTO_ARRAY[] = {
    new Tuto("Apprenez à créer votre site web avec HTML5 et CSS3", "http://www.siteduzero.com/tutoriel-3-13666-apprenez-a-creer-votre-site-web-avec-html5-et-css3.html"),
    new Tuto("Apprenez à programmer en C !", "http://www.siteduzero.com/tutoriel-3-14189-apprenez-a-programmer-en-c.html"), 
    new Tuto("Créez des applications pour Android", "http://www.siteduzero.com/tutoriel-3-554364-creez-des-applications-pour-android.html")
  };

  // Intitulé de l'extra qui contient la direction du défilé
  private final static String EXTRA_DIRECTION = "extraDirection";

  // La valeur pour défiler vers la gauche
  private final static String EXTRA_PREVIOUS = "previous";

  // La valeur pour défiler vers la droite
  private final static String EXTRA_NEXT = "next";

  // Intitulé de l'extra qui contient l'indice actuel dans le tableau des tutos
  private final static String EXTRA_INDICE = "extraIndice";

  // Action qui indique qu'on essaie d'ouvrir un tuto sur internet
  private final static String ACTION_OPEN_TUTO = "sdz.chapitreQuatre.tutowidget.action.OPEN_TUTO";

  // Indice actuel dans le tableau des tutos
  private int indice = 0;

  @Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);

    // Petite astuce : permet de garder la longueur du tableau sans accéder plusieurs fois à l'objet, d'où optimisation
    final int length = appWidgetIds.length;
    for (int i = 0 ; i < length ; i++) {
      // On récupère le RemoteViews qui correspond à l'AppWidget
      RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);

      // On met le bon texte dans le bouton
      views.setTextViewText(R.id.link, TUTO_ARRAY[indice].getIntitule());

      // La prochaine section est destinée au bouton qui permet de passer au tuto suivant
      //********************************************************
      //*******************NEXT*********************************
      //********************************************************
      Intent nextIntent = new Intent(context, TutoWidget.class);

      // On veut que l'intent lance la mise à jour
      nextIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);

      // On n'oublie pas les identifiants
      nextIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

      // On rajoute la direction
      nextIntent.putExtra(EXTRA_DIRECTION, EXTRA_NEXT);

      // Ainsi que l'indice
      nextIntent.putExtra(EXTRA_INDICE, indice);

      // Les données inutiles mais qu'il faut rajouter
      Uri data = Uri.withAppendedPath(Uri.parse("WIDGET://widget/id/"), String.valueOf(R.id.next));
      nextIntent.setData(data);

      // On insère l'intent dans un PendingIntent
      PendingIntent nextPending = PendingIntent.getBroadcast(context, 0, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT);

      // Et on l'associe à l'activation du bouton
      views.setOnClickPendingIntent(R.id.next, nextPending);

      // La prochaine section est destinée au bouton qui permet de passer au tuto précédent
      //********************************************************
      //*******************PREVIOUS*****************************
      //********************************************************

      Intent previousIntent = new Intent(context, TutoWidget.class);

      previousIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
      previousIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
      previousIntent.putExtra(EXTRA_DIRECTION, EXTRA_PREVIOUS);
      previousIntent.putExtra(EXTRA_INDICE, indice);

      data = Uri.withAppendedPath(Uri.parse("WIDGET://widget/id/"), String.valueOf(R.id.previous));
      previousIntent.setData(data);

      PendingIntent previousPending = PendingIntent.getBroadcast(context, 1, previousIntent, PendingIntent.FLAG_UPDATE_CURRENT);

      views.setOnClickPendingIntent(R.id.previous, previousPending);


      // La section suivante est destinée à l'ouverture d'un tuto dans le navigateur
      //********************************************************
      //*******************LINK*********************************
      //********************************************************
      // L'intent ouvre cette classe même…
      Intent linkIntent = new Intent(context, TutoWidget.class);

      // Action l'action ACTION_OPEN_TUTO
      linkIntent.setAction(ACTION_OPEN_TUTO);
      // Et l'adresse du site à visiter
      linkIntent.setData(TUTO_ARRAY[indice].getAdresse());

      // On ajoute l'intent dans un PendingIntent
      PendingIntent linkPending = PendingIntent.getBroadcast(context, 2, linkIntent, PendingIntent.FLAG_UPDATE_CURRENT);
      views.setOnClickPendingIntent(R.id.link, linkPending);

      // Et il faut mettre à jour toutes les vues
      appWidgetManager.updateAppWidget(appWidgetIds[i], views);
    }
  }

  @Override
  public void onReceive(Context context, Intent intent) {
    // Si l'action est celle d'ouverture du tutoriel
    if(intent.getAction().equals(ACTION_OPEN_TUTO)) {
      Intent link = new Intent(Intent.ACTION_VIEW);
      link.setData(intent.getData());
      link.addCategory(Intent.CATEGORY_DEFAULT);
      // Comme on ne se trouve pas dans une activité, on demande à créer une nouvelle tâche
      link.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(link);
    } else {
      // Sinon, s'il s'agit d'une demande de mise à jour
      // On récupère l'indice passé en extra, ou -1 s'il n'y a pas d'indice
      int tmp = intent.getIntExtra(EXTRA_INDICE, -1);

      // S'il y avait bien un indice passé
      if(tmp != -1) {
        // On récupère la direction
        String extra = intent.getStringExtra(EXTRA_DIRECTION);
        // Et on calcule l'indice voulu par l'utilisateur
        if (extra.equals(EXTRA_PREVIOUS)) {
          indice = (tmp - 1) % TUTO_ARRAY.length;
          if(indice < 0)
            indice += TUTO_ARRAY.length;
        } else if(extra.equals(EXTRA_NEXT))
          indice = (tmp + 1) % TUTO_ARRAY.length;
      }
    }

    // On revient au traitement naturel du Receiver, qui va lancer onUpdate s'il y a demande de mise à jour
    super.onReceive(context, intent);
  }

}

Enfin, on déclare le tout dans le Manifest :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="sdz.chapitrequatre.tutowidget"
  android:versionCode="1"
  android:versionName="1.0" >

  <uses-sdk
    android:minSdkVersion="7"
    android:targetSdkVersion="7" />

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <receiver
      android:name=".TutoWidget"
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="sdz.chapitreQuatre.tutowidget.action.OPEN_TUTO" />
      </intent-filter>

      <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widget_provider_info" />
    </receiver>
  </application>

</manifest>

  • Un AppWidget est une extension de votre application. Afin que l'utilisateur ne soit pas désorienté, adoptez la même charte graphique que votre application.
  • Les seules vues utilisables pour un widget sont les vues RemoteViews.
  • La déclaration d'un AppWidget se fait dans un élément appwidget-provider à partir d'un fichier XML AppWidgetProviderInfo.
  • La super classe de notre AppWidget sera un AppWidgetProvider. Il s'occupera de gérer tous les évènements sur le cycle de vie de notre AppWidget. Cette classe dérive de BroadcastReceiver, elle va donc recevoir les divers broadcast intents qui sont émis et qui sont destinés à l'AppWidget.
  • Pour déclarer notre AppWidget dans le manifest, nous allons créer un élément receiver auquel nous ajoutons un élément intent-filterpour lancer notre AppWidget et un élément meta-data pour définir l'AppWidgetProviderInfo.