Présentation
- Widget : composant graphique d'une application embarquable dans une autre application
- Utilisation typique par intégration sur l'écran d'accueil
-
Principaux types de widgets :
- Widgets d'information affichant certaines données de l'application (par exemple prévisions météo, trafic routier...)
- Widgets de contrôle permettant d'agir sur l'application sans ouvrir de nouvelle activité (lancer des actions, changer un état...)
- Widget de parcours de collections permettant d'itérer sur des éléments (visualisation de gallerie photo par exemple)
Principe de fonctionnement
- Lien vers le document de référence de la documentation Android
-
Permet d'embarquer dans une application B hôte un composant graphique controlé par une application A
- Généralement l'application B est l'écran d'accueil
-
A contrôle à distance le composant graphique grâce à un BroadcastReceiver (exécuté dans A)
- Le widget peut être rafraîchi avec une périodicité fixe par le système (inconvénient : peu flexible, mécanisme fonctionnant même en état de veille)
- Il est possible aussi d'envoyer manuellement un Intent au BrodcastReceiver lié au widget avec une action APPWIDGET_UPDATE (en utlisant éventuellement un AlarmManager)
-
AppWidgetProvider hérite du BroadcastReceiver et facilite le code de gestion du widget applicatif :
- On redéfinit void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
- Les méthodes onEnabled(), onDisabled() lorsque le 1er exemplaire du widget est intégré ou lorsque le dernier exemplaire est enlevé ; pour chaque exemplaire enlevé on appelle onDeleted()
-
AppWidgetManager permet dans onUpdate() de mettre à jour un des composants graphiques du widget par appel de méthode instrospectif (pas d'accès direct à l'objet Java du composant graphique car celui-ci est hébergé "à distance" sur B) : changement de propriété booléenne, texte, entière...
- Attention, seules les méthodes annotées par @RemotableViewMethod sont appelables à distance
- On peut associer des PendingIntent à exécuter avec les droits de A pour réagir aux événements envoyés sur le widget (clic sur unes des vues)
Feuille de route pour créer un widget inter-applicatif
-
Créer le layout XML du widget
- Seuls certains layouts sont utilisables : FrameLayout, LinearLayout, RelativeLayout, GridLayout (ConstraintLayout non supporté)
- Ne peut contenir que certaines vues compatibles avec RemoteView telles que TextView, Button, ImageView, ProgressBar (EditText par exemple n'est pas compatible)
- Créer un fichier XML de description des métadonnées du widget dans res/xml
- Créer le AppWidgetProvider pour programmer le comportement à adopter pour mettre à jour le widget ; le déclarer dans le manifeste associé aux actions gérées
-
Provoquer depuis le code de l'application A des mises à jour du widget (en envoyant un Intent vers l'AppWidgetManager)
- Mise à jour des propriétés des composants graphiques
- Enregistrement/suppression de listeners liés à l'envoi d'Intent
- Les composants graphiques basés sur des adaptateurs tels que ListView ou GridView peuvent être employés en utilisant un RemoteViewFactory pour la mise à jour de l'adaptateur
Exemple : widget affichant le temps écoulé depuis le démarrage
Création du layout (simple) pour le widget :
<?xml version="1.0" encoding="utf-8"?> <!-- Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/elapsedTimeView" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#00FFFFFF" android:text="TextView" /> </LinearLayout>
Implantation du receiver réalisant la mise à jour du widget lorsqu'un événement de mise à jour est reçu :
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.elapsedtimewidget import android.app.LauncherActivity import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent import android.os.SystemClock import android.widget.RemoteViews import fr.upem.coursand.R /** Implementation of an appwidget displaying the elapsed time since boot */ class ElapsedTimeAppWidgetProvider: AppWidgetProvider() { override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { // Perform this loop procedure for each App Widget that belongs to this provider appWidgetIds.forEach { appWidgetId -> // Create an Intent to launch the main activity of Coursand val pendingIntent: PendingIntent = Intent(context, LauncherActivity::class.java) .let { PendingIntent.getActivity(context, 0, it, 0) } // Get the layout for the App Widget // Update the elapsed time of the TextView and a click listener on it launching // the main activity of coursand val views: RemoteViews = RemoteViews(context.packageName, R.layout.widget_elapsedtime).apply { val elapsedMinutes = SystemClock.elapsedRealtime() / 1000 / 60 // in minutes this.setTextViewText(R.id.elapsedTimeView, "$elapsedMinutes minutes since boot") setOnClickPendingIntent(R.id.elapsedTimeView, pendingIntent) } // Tell the AppWidgetManager to perform an update on the current app widget appWidgetManager.updateAppWidget(appWidgetId, views) } } }
Ecriture du fichier res/xml/elapsedtime_appwidget_info.xml d'informations sur le widget :
<!-- Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License --> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="40dp" android:minHeight="40dp" android:updatePeriodMillis="60000" android:previewImage="@drawable/ic_menu" android:initialLayout="@layout/widget_elapsedtime" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen"> </appwidget-provider>
Déclaration dans la manifeste du widget :
<manifest ...> ... <application> ... <receiver android:name=".elapsedtimewidget.ElapsedTimeAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/elapsedtime_appwidget_info" /> </receiver> </application> </manifest>