Tutoriel sur la création de Widget sous Android

Développer des widgets Android

Cet article explique comment créer des widgets Android. Il est basé sur Eclipse 4.2, Java 1.6 et Android 4.2.

Nous remercions Lars Vogel qui nous a aimablement autorisé à traduire et héberger cet article.

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez Donner une note à l'article (5).

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Prérequis

Ce qui suit suppose que vous avez déjà de l'expérience dans le développement d'applications Android basiques. Veuillez consulter le tutoriel Android. On utilise aussi en partie les services Android. Vous en trouvez une présentation dans le tutoriel Android Service.

2. Les widgets Android

2-1. Vue d'ensemble des appWidgets

Les widgets sont de petites applications qui peuvent être placées sur un hôte de widgets, généralement l'écran d'accueil ou l'écran de verrouillage de votre appareil Android.

Un widget s'exécute dans le cadre du processus de son hôte. Cela exige qu'il conserve les autorisations de son application.

Les widgets utilisent RemoteViews pour créer leur interface utilisateur. Une RemoteView peut être exécutée par un autre processus, avec les mêmes autorisations que l'application d'origine. De cette façon, le widget s'exécute avec les autorisations de son application déterminante.

L'interface utilisateur d'un Widget est définie par un récepteur de diffusion. Ce récepteur déploie sa mise en page en un objet de type RemoteViews. Cet objet est livré à Android, qui le remet à l'application d'écran d'accueil.

2-2. Les étapes pour créer un widget

Pour créer un widget, vous devez :

  • définir un fichier de mise en page ;
  • créer un fichier XML (AppWidgetProviderInfo) qui décrit les propriétés du widget, par exemple la taille ou la fréquence fixe de mise à jour ;
  • créer un BroadcastReceiver, utilisé pour générer l'interface utilisateur du widget ;
  • saisir la configuration du Widget dans le fichier AndroidManifest.xml ;
  • optionnel : vous pouvez spécifier une activité de configuration, appelée lorsqu'une nouvelle instance du widget est ajoutée à l'hôte.

2-3. La taille du widget

Avant Android 3.1, un widget occupait toujours un nombre fixe de cellules sur l'écran d'accueil. Une cellule est généralement utilisée pour afficher l'icône d'une application. Comme règle de calcul, vous devez définir la taille du widget avec la formule : ((Nombre de colonnes / lignes) * 74) - 2. Ce sont des pixels indépendants du périphérique et le -2 est utilisé pour éviter les erreurs d'arrondi.

À partir d'Android 3.1, un widget peut avoir une taille flexible et l'utilisateur peut l'agrandir ou le diminuer. Pour activer cette caractéristique, vous pouvez utiliser l'attribut android:resizeMode="horizontal|vertical" dans le fichier XML de configuration du widget.

3. La création du récepteur de diffusion pour le widget

3-1. Créer et configurer un widget

Pour enregistrer un widget, vous créez un récepteur de diffusion avec un filtre d'intention pour l'action android.appwidget.action.APPWIDGET_UPDATE.

 
Sélectionnez
<receiver
       android:icon="@drawable/icon"
       android:label="Example Widget"
       android:name="MyWidgetProvider" >
       <intent-filter >
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
       </intent-filter>

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

Le récepteur peut obtenir une étiquette et une icône. Celles-ci sont utilisées dans la liste de widgets disponibles du lanceur Android.

Vous spécifiez également les métadonnées du widget par l'attribut android:name="android.appwidget.provider". Le fichier de configuration référencé par ces métadonnées contient les paramètres de configuration du widget, par exemple l'interface de mise à jour, la taille et la mise en page initiale du widget.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="72dp"
    android:minWidth="146dp"
    android:updatePeriodMillis="1800000" >

</appwidget-provider>

3-2. Vues et mises en page disponibles

L'utilisation des classes View par un widget est restreinte. Pour la mise en page, vous pouvez utiliser les classes FrameLayout, LinearLayout et RelativeLayout. Comme vues, vous disposez d'AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar et TextView.

À partir d'Android 3.0, plusieurs vues sont disponibles : GridView, ListView, StackView, ViewFlipper et AdapterViewFlipper. Ces adaptateurs de vues exigent que vous définissiez un collection view widget, décrit plus loin.

La seule interaction possible entre un widget et les vues s'effectue par un événement OnClickListener, qui peut être enregistré sur le widget et est déclenché par l'utilisateur.

3-3. AppWidgetProvider

Votre implémentation de BroadcastReceiver étend généralement la classe AppWidgetProvider.

La classe AppWidgetProvider implémente la méthode OnReceive(), extrait les informations nécessaires et appelle les méthodes de cycle de vie du widget ci-dessous.

Comme vous pouvez ajouter plusieurs instances d'un widget à l'écran d'accueil, vous avez des méthodes de cycle de vie appelées uniquement pour la première instance ajoutée à (ou supprimée de) l'écran d'accueil et d'autres qui sont appelées pour chaque instance de votre widget.

Table 1. Méthodes de cycle de vie

Méthode

Description

onEnabled()

Appelée la première fois où une instance de votre widget est ajoutée à l'écran d'accueil.

onDisabled()

Appelée dès que la dernière instance de votre widget est supprimée de l'écran d'accueil.

onUpdate()

Appelée lors de chaque mise à jour du widget. Contient les identifiants appWidgetIds des widgets pour lesquels est nécessaire une mise à jour. À noter qu'il pourrait s'agir de toutes les instances AppWidget pour ce fournisseur, ou seulement d'un sous-ensemble d'entre elles, comme le spécifie la Javadoc de la méthode. Par exemple, si plus d'un widget est ajouté à l'écran d'accueil, seul le dernier est modifié (jusqu'à la réinstallation).

onDeleted()

L'instance du widget est supprimée de l'écran d'accueil.

3-4. Récepteur et traitement asynchrone

Un widget a les mêmes restrictions d'exécution qu'un récepteur normal de diffusion, c'est-à-dire qu'il n'a que cinq secondes pour terminer son traitement.

Un récepteur (widget) devrait donc dérouler des opérations chronophages dans un service et effectuer la mise à jour des widgets à partir du service.

4. Mises à jour du widget

Un widget obtient ses données sur un horaire périodique. Il existe deux méthodes pour mettre à jour un widget : l'une est basée sur un fichier de configuration XML et l'autre, sur le service Android AlarmManager.

Dans le fichier de configuration du widget, vous pouvez spécifier un intervalle fixe de mise à jour. Le système va se réveiller après cet intervalle de temps et appeler votre récepteur de diffusion pour mettre à jour le widget. L'intervalle de mise à jour le plus court est de 1800000 millisecondes (30 minutes).

L'AlarmManager vous permet d'être plus efficace en termes de ressources et d'avoir une plus grande fréquence de mise à jour. Pour utiliser cette approche, vous définissez un service et planifiez ce service régulièrement via AlarmManager. Le service met à jour le widget.

Veuillez noter qu'une fréquence de mise à jour plus élevée réveillera le téléphone du mode économie d'énergie. En conséquence, votre widget consomme plus d'énergie.

5. Exercice : widget avec intervalle fixe de mise à jour

5-1. Objectif

Dans l'exercice suivant, vous créez un widget qui affiche un nombre aléatoire. Celui-ci est mis à jour toutes les 30 minutes. Vous enregistrez également un OnClickListener, pour que le widget soit mis à jour chaque fois que l'utilisateur clique dessus.

Le widget devrait ressembler à ceci :

Image non disponible

5-2. Création du projet et implémentation du widget

Créez un nouveau projet Android appelé de.vogella.android.widget.example, avec une activité dans le paquetage de.vogella.android.widget.example.

Créez un nouveau fichier myshape.xml dans le répertoire /res/drawable. Ce fichier définit le drawable utilisé comme arrière-plan pour le widget.

 
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <stroke
        android:width="2dp"
        android:color="#FFFFFFFF" />

    <gradient
        android:angle="225"
        android:endColor="#DD2ECCFA"
        android:startColor="#DD000000" />

    <corners
        android:bottomLeftRadius="7dp"
        android:bottomRightRadius="7dp"
        android:topLeftRadius="7dp"
        android:topRightRadius="7dp" />

</shape>

Définissez le fichier widget_layout.xml suivant sous le répertoire res/layout.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="8dip"
    android:background="@drawable/myshape" >

    <TextView
        android:id="@+id/update"
        style="@android:style/TextAppearance.Medium"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:gravity="center_horizontal|center_vertical"
        android:layout_margin="4dip"
        android:text="Static Text" >
    </TextView>

</LinearLayout>

Créez le fichier widget_info.xml de métadonnées AppWidgetProvider, via Fichier -> Nouveau -> Android -> fichier XML Android.

Image non disponible
Image non disponible
 
Sélectionnez
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
      xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="72dp"
    android:minWidth="300dp"
    android:updatePeriodMillis="300000" >

</appwidget-provider>

Créez la classe réceptrice suivante, qui est appelée pendant les mises à jour.

 
Sélectionnez
package de.vogella.android.widget.example;

import java.util.Random;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;

public class MyWidgetProvider extends AppWidgetProvider {

  private static final String ACTION_CLICK = "ACTION_CLICK";

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

    // Retrouver tous les id
    ComponentName thisWidget = new ComponentName(context,
        MyWidgetProvider.class);
    int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
    for (int widgetId : allWidgetIds) {
      // créer des données aléatoires
      int number = (new Random().nextInt(100));

      RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
          R.layout.widget_layout);
      Log.w("WidgetExample", String.valueOf(number));
      // Définir le texte
      remoteViews.setTextViewText(R.id.update, String.valueOf(number));

      // Enregistrer un onClickListener
      Intent intent = new Intent(context, MyWidgetProvider.class);

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

      PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
          0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
      remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent);
      appWidgetManager.updateAppWidget(widgetId, remoteViews);
    }
  }
}

Ouvrez AndroidManifest.xml et déclarez votre widget comme ci-dessous.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.vogella.android.widget.example"
    android:versionCode="1"
    android:versionName="1.0" >

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <receiver android:name="MyWidgetProvider" >
            <intent-filter >
                <action 
                    android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

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

    <uses-sdk android:minSdkVersion="8" />

</manifest>

Cet attribut indique qu'AppWidgetProvider accepte la diffusion d'ACTION_APPWIDGET_UPDATE et spécifie les métadonnées pour le widget.

5-3. Validation

Déployez l'application sur votre appareil Android. Une fois qu'elle a été déployée, utilisez le lanceur Android pour installer votre nouveau widget sur l'écran d'accueil et le tester.

Image non disponible
Image non disponible

6. Collection view widgets

Collection view widgets ajoute le support pour l'utilisation des classes ListView, StackView et GridView dans les widgets.

Pour un widget de collection de vues, vous avez besoin de deux fichiers de mise en page : l'un pour le widget et l'autre pour chaque élément de la collection de widgets.

Les éléments du widget sont remplis par une instance de la classe RemoteViewsFactory.

Cette classe est fournie par un service Android qui étend la classe RemoteViewsService. Ce service nécessite l'autorisation android.permission.BIND_REMOTEVIEWS.

Pour connecter vos vues avec le service, vous utilisez la méthode onUpdate() de votre implémentation du widget.

Vous définissez une intention qui pointe vers le service et utilisez la méthode setRemoteAdapter sur la classe RemoteViews.

 
Sélectionnez
Intent intent = new Intent(context, YourRemoteViewsService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
views.setRemoteAdapter(apppWidgetId,
  R.id.widget_your_id_to_collectionview,
  intent)

7. Activation d'un widget pour l'écran de verrouillage

Depuis Android 4.2, il est possible d'ajouter des applications widgets à l'écran de verrouillage d'un appareil Android. Pour cela, il vous suffit de déclarer un widget supplémentaire qui assure le support de la catégorie « keyguard » de l'attribut android:widgetCategory dans le fichier XML AppWidgetProviderInfo. Le code suivant montre un exemple.

 
Sélectionnez
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:widgetCategory="keyguard|home_screen"
  ...
>
...
</appwidget-provider>

Dans cet exemple, vous déclarez un widget qui soutient les deux écrans, d'accueil et de verrouillage. Si vous recompilez et lancez votre application maintenant, vous serez déjà en mesure d'ajouter le widget à l'écran de verrouillage.

Vous pouvez également détecter une catégorie widget lors de l'exécution. Pour cela, dans la méthode AppWidgetProvider.onUpdate(), vous pouvez vérifier l'option de la catégorie d'un widget avec le code suivant :

 
Sélectionnez
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);

int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
boolean isLockScreen = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;

En utilisant cette technique, vous pouvez décider, au moment de l'exécution, si les widgets de votre application auront un aspect différent lorsqu'ils sont hébergés sur l'écran de verrouillage.

Tout comme vous avez utilisé l'attribut android:initialLayout pour définir une mise en page initiale des widgets sur l'écran d'accueil, vous pouvez utiliser un nouvel attribut android:initialKeyguardLayout pour l'écran de verrouillage dans le fichier XML AppWidgetProviderInfo. Cette mise en page s'affiche immédiatement après l'ajout d'un widget et sera remplacée par la disposition réelle une fois le widget initialisé.

8. Exercice : mettre à jour un widget à partir d'un service

Ce qui suit va montrer l'utilisation d'un service pour mettre à jour le widget.

Créez la classe UpdateWidgetService suivante dans votre projet.

 
Sélectionnez
package de.vogella.android.widget.example;

import java.util.Random;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;

public class UpdateWidgetService extends Service {
  private static final String LOG = "de.vogella.android.widget.example";

  @Override
  public void onStart(Intent intent, int startId) {
    Log.i(LOG, "Called");
    // créer des données aléatoires

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
        .getApplicationContext());

    int[] allWidgetIds = intent
        .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

    ComponentName thisWidget = new ComponentName(getApplicationContext(),
        MyWidgetProvider.class);
    int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
    Log.w(LOG, "From Intent" + String.valueOf(allWidgetIds.length));
    Log.w(LOG, "Direct" + String.valueOf(allWidgetIds2.length));

    for (int widgetId : allWidgetIds) {
      // créer des données aléatoires
      int number = (new Random().nextInt(100));

      RemoteViews remoteViews = new RemoteViews(this
          .getApplicationContext().getPackageName(),
          R.layout.widget_layout);
      Log.w("WidgetExample", String.valueOf(number));
      // Définir le texte
      remoteViews.setTextViewText(R.id.update,
          "Random: " + String.valueOf(number));

      // Enregistrer un onClickListener
      Intent clickIntent = new Intent(this.getApplicationContext(),
          MyWidgetProvider.class);

      clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
      clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
          allWidgetIds);

      PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, clickIntent,
          PendingIntent.FLAG_UPDATE_CURRENT);
      remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent);
      appWidgetManager.updateAppWidget(widgetId, remoteViews);
    }
    stopSelf();

    super.onStart(intent, startId);
  }

  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
}

Ajoutez cette classe comme un Service dans votre fichier AndroidManifest.xml.

 
Sélectionnez
<service android:name=".UpdateWidgetService"></service>

Modifiez MyWidgetProvider comme ci-dessous. Maintenant, il va seulement construire et démarrer le service.

 
Sélectionnez
package de.vogella.android.widget.example;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyWidgetProvider extends AppWidgetProvider {

  private static final String LOG = "de.vogella.android.widget.example";

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

    Log.w(LOG, "onUpdate method called");
    // Retrouver tous les ids
    ComponentName thisWidget = new ComponentName(context,
        MyWidgetProvider.class);
    int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

    // Construire l'intention pour appeler le service
    Intent intent = new Intent(context.getApplicationContext(),
        UpdateWidgetService.class);
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);

    // Mettre à jour les widgets via le service
    context.startService(intent);
  }
}

Une fois appelé, ce service mettra à jour tous les widgets. Vous pouvez cliquer sur l'un des widgets pour les mettre tous à jour.

9. Liens

10. Remerciements Developpez

Vous pouvez retrouver l'article original à l'adresse Android Live Wallpaper - Tutorial. Nous remercions Lars Vogel qui nous a aimablement autorisés à traduire et héberger ses articles.

Nous remercions aussi Mishulyna pour sa traduction, ainsi que jacques_jean pour sa relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par Lars Vogel et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.