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>