I. 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.
II. Les widgets Android▲
II-A. 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.
II-B. 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.
II-C. 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.
III. La création du récepteur de diffusion pour le widget▲
III-A. 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.
<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.
<?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>
III-B. 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.
III-C. 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. |
III-D. 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.
IV. 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.
V. Exercice : widget avec intervalle fixe de mise à jour▲
V-A. 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 :
V-B. 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.
<?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.
<?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.
<?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.
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.
<?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.
V-C. 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.
VI. 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.
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)
VII. 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.
<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 :
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é.
VIII. 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.
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.
<service
android
:
name
=
".UpdateWidgetService"
></service>
Modifiez MyWidgetProvider comme ci-dessous. Maintenant, il va seulement construire et démarrer le service.
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.
IX. Liens▲
X. 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.