Les widgets de collection sont spécialisés dans l'affichage de nombreux éléments du même type, tels que des collections d'images d'une application Galerie, des articles d'une application d'actualités ou des messages d'une application de communication. Les widgets de collection se concentrent généralement sur deux cas d'utilisation: parcourir la collection et ouvrir un élément de la collection dans sa vue détaillée. Les widgets de collection peuvent défiler verticalement.
Ces widgets utilisent RemoteViewsService
pour afficher des collections basées sur des données distantes, par exemple provenant d'un fournisseur de contenu. Le widget présente les données à l'aide de l'un des types de vue suivants, appelés vues de collection:
ListView
- Vue qui affiche les éléments dans une liste à défilement vertical.
GridView
- Vue qui affiche les éléments dans une grille à défilement bidimensionnelle.
StackView
- Vue de cartes empilées (un peu comme un rolodex), où l'utilisateur peut faire glisser la carte de devant vers le haut ou vers le bas pour afficher la carte précédente ou suivante, respectivement.
AdapterViewFlipper
- Un
ViewAnimator
simple compatible avec un adaptateur qui effectue une animation entre deux vues ou plus. Un seul enfant s'affiche à la fois.
Étant donné que ces vues de collection affichent des collections basées sur des données distantes, elles utilisent un Adapter
pour lier leur interface utilisateur à leurs données. Un Adapter
lie des éléments individuels d'un ensemble de données à des objets View
individuels.
Étant donné que ces vues de collection sont prises en charge par des adaptateurs, le framework Android doit inclure une architecture supplémentaire pour permettre leur utilisation dans les widgets. Dans le contexte d'un widget, Adapter
est remplacé par RemoteViewsFactory
, qui est un wrapper mince autour de l'interface Adapter
. Lorsqu'un élément spécifique de la collection est demandé, RemoteViewsFactory
crée et renvoie l'élément de la collection en tant qu'objet RemoteViews
. Pour inclure une vue de collection dans votre widget, implémentez RemoteViewsService
et RemoteViewsFactory
.
RemoteViewsService
est un service qui permet à un adaptateur distant de demander des objets RemoteViews
. RemoteViewsFactory
est une interface pour un adaptateur entre une vue de collection (par exemple, ListView
, GridView
et StackView
) et les données sous-jacentes de cette vue. À partir de l'exemple StackWidget
, voici un exemple de code standard pour implémenter ce service et cette interface:
Kotlin
class StackWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { return StackRemoteViewsFactory(this.applicationContext, intent) } } class StackRemoteViewsFactory( private val context: Context, intent: Intent ) : RemoteViewsService.RemoteViewsFactory { // See the RemoteViewsFactory API reference for the full list of methods to // implement. }
Java
public class StackWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { // See the RemoteViewsFactory API reference for the full list of methods to // implement. }
Exemple d'application
Les extraits de code de cette section sont également tirés de l'exemple StackWidget
:
Cet exemple consiste en une pile de dix vues affichant les valeurs de zéro à neuf. L'exemple de widget présente les comportements principaux suivants:
L'utilisateur peut faire glisser verticalement la vue supérieure du widget pour afficher la vue suivante ou précédente. Il s'agit d'un comportement
StackView
intégré.Sans aucune interaction de l'utilisateur, le widget fait défiler ses vues automatiquement, comme un diaporama. Cela est dû au paramètre
android:autoAdvanceViewId="@id/stack_view"
dans le fichierres/xml/stackwidgetinfo.xml
. Ce paramètre s'applique à l'ID de vue, qui est dans ce cas l'ID de la vue de pile.Si l'utilisateur appuie sur la vue supérieure, le widget affiche le message
Toast
"Vue touchée n", où n est l'indice (position) de la vue touchée. Pour en savoir plus sur l'implémentation de comportements, consultez la section Ajouter un comportement à des éléments individuels.
Implémenter des widgets avec des collections
Pour implémenter un widget avec des collections, suivez la procédure d'implémentation d'un widget, puis effectuez quelques étapes supplémentaires : modifiez le fichier manifeste, ajoutez une vue de collection à la mise en page du widget et modifiez votre sous-classe AppWidgetProvider
.
Fichier manifeste pour les widgets avec des collections
En plus des exigences listées dans Déclarer un widget dans le fichier manifeste, vous devez permettre aux widgets avec des collections de se lier à votre RemoteViewsService
. Pour ce faire, déclarez le service dans votre fichier manifeste avec l'autorisation BIND_REMOTEVIEWS
.
Cela empêche les autres applications d'accéder librement aux données de votre widget.
Par exemple, lorsque vous créez un widget qui utilise RemoteViewsService
pour renseigner une vue de collection, l'entrée du fichier manifeste peut se présenter comme suit:
<service android:name="MyWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
Dans cet exemple, android:name="MyWidgetService"
fait référence à votre sous-classe de RemoteViewsService
.
Mise en page des widgets avec des collections
La principale exigence concernant le fichier XML de mise en page de votre widget est qu'il inclue l'une des vues de collection: ListView
, GridView
, StackView
ou AdapterViewFlipper
. Voici le fichier widget_layout.xml
de l'exemple StackWidget
:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<StackView
android:id="@+id/stack_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:loopViews="true" />
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@drawable/widget_item_background"
android:textColor="#ffffff"
android:textStyle="bold"
android:text="@string/empty_view_text"
android:textSize="20sp" />
</FrameLayout>
Notez que les vues vides doivent être des frères et sœurs de la vue de collection pour laquelle la vue vide représente l'état vide.
En plus du fichier de mise en page de l'ensemble du widget, créez un autre fichier de mise en page qui définit la mise en page de chaque élément de la collection (par exemple, une mise en page pour chaque livre d'une collection de livres). L'exemple StackWidget
ne comporte qu'un seul fichier de mise en page d'éléments, widget_item.xml
, car tous les éléments utilisent la même mise en page.
Classe AppWidgetProvider pour les widgets avec des collections
Comme pour les widgets standards, la majeure partie du code de votre sous-classe AppWidgetProvider
se trouve généralement dans onUpdate()
.
La principale différence dans votre implémentation pour onUpdate()
lors de la création d'un widget avec des collections est que vous devez appeler setRemoteAdapter()
. Cela indique à la vue de la collection où obtenir ses données.
RemoteViewsService
peut ensuite renvoyer votre implémentation de RemoteViewsFactory
, et le widget peut diffuser les données appropriées. Lorsque vous appelez cette méthode, transmettez un intent qui pointe vers votre implémentation de RemoteViewsService
et l'ID de widget qui spécifie le widget à mettre à jour.
Par exemple, voici comment l'exemple StackWidget
implémente la méthode de rappel onUpdate()
pour définir RemoteViewsService
comme adaptateur distant pour la collection de widgets:
Kotlin
override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { // Update each of the widgets with the remote adapter. appWidgetIds.forEach { appWidgetId -> // Set up the intent that starts the StackViewService, which // provides the views for this collection. val intent = Intent(context, StackWidgetService::class.java).apply { // Add the widget ID to the intent extras. putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) } // Instantiate the RemoteViews object for the widget layout. val views = RemoteViews(context.packageName, R.layout.widget_layout).apply { // Set up the RemoteViews object to use a RemoteViews adapter. // This adapter connects to a RemoteViewsService through the // specified intent. // This is how you populate the data. setRemoteAdapter(R.id.stack_view, intent) // The empty view is displayed when the collection has no items. // It must be in the same layout used to instantiate the // RemoteViews object. setEmptyView(R.id.stack_view, R.id.empty_view) } // Do additional processing specific to this widget. appWidgetManager.updateAppWidget(appWidgetId, views) } super.onUpdate(context, appWidgetManager, appWidgetIds) }
Java
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Update each of the widgets with the remote adapter. for (int i = 0; i < appWidgetIds.length; ++i) { // Set up the intent that starts the StackViewService, which // provides the views for this collection. Intent intent = new Intent(context, StackWidgetService.class); // Add the widget ID to the intent extras. intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); // Instantiate the RemoteViews object for the widget layout. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); // Set up the RemoteViews object to use a RemoteViews adapter. // This adapter connects to a RemoteViewsService through the specified // intent. // This is how you populate the data. views.setRemoteAdapter(R.id.stack_view, intent); // The empty view is displayed when the collection has no items. // It must be in the same layout used to instantiate the RemoteViews // object. views.setEmptyView(R.id.stack_view, R.id.empty_view); // Do additional processing specific to this widget. appWidgetManager.updateAppWidget(appWidgetIds[i], views); } super.onUpdate(context, appWidgetManager, appWidgetIds); }
Persistance des données
Comme décrit sur cette page, la sous-classe RemoteViewsService
fournit le RemoteViewsFactory
utilisé pour renseigner la vue de collection distante.
Procédez comme suit:
Sous-classe
RemoteViewsService
.RemoteViewsService
est le service par lequel un adaptateur distant peut demanderRemoteViews
.Dans votre sous-classe
RemoteViewsService
, incluez une classe qui implémente l'interfaceRemoteViewsFactory
.RemoteViewsFactory
est une interface pour un adaptateur entre une vue de collection distante (par exemple,ListView
,GridView
ouStackView
) et les données sous-jacentes de cette vue. Votre implémentation est chargée de créer un objetRemoteViews
pour chaque élément de l'ensemble de données. Cette interface est un wrapper fin autour deAdapter
.
Vous ne pouvez pas vous fier à une seule instance de votre service ni aux données qu'il contient pour qu'elles persistent. Ne stockez pas de données dans votre RemoteViewsService
, sauf si elles sont statiques. Si vous souhaitez que les données de votre widget persistent, la meilleure approche consiste à utiliser un ContentProvider
dont les données persistent au-delà du cycle de vie du processus. Par exemple, un widget de supermarché peut stocker l'état de chaque élément de la liste de courses dans un emplacement persistant, tel qu'une base de données SQL.
Le contenu principal de l'implémentation de RemoteViewsService
est son RemoteViewsFactory
, décrit dans la section suivante.
Interface RemoteViewsFactory
Votre classe personnalisée qui implémente l'interface RemoteViewsFactory
fournit au widget les données des éléments de sa collection. Pour ce faire, il combine votre fichier de mise en page XML de l'élément de widget avec une source de données. Cette source de données peut être n'importe quoi, d'une base de données à un tableau simple. Dans l'exemple StackWidget
, la source de données est un tableau de WidgetItems
. RemoteViewsFactory
fonctionne comme un adaptateur pour coller les données à la vue de la collection à distance.
Les deux méthodes les plus importantes que vous devez implémenter pour votre sous-classe RemoteViewsFactory
sont onCreate()
et getViewAt()
.
Le système appelle onCreate()
lors de la création de votre usine pour la première fois.
C'est ici que vous configurez les connexions ou les curseurs à votre source de données. Par exemple, l'exemple StackWidget
utilise onCreate()
pour initialiser un tableau d'objets WidgetItem
. Lorsque votre widget est actif, le système accède à ces objets à l'aide de leur position d'index dans le tableau et affiche le texte qu'ils contiennent.
Voici un extrait de l'implémentation RemoteViewsFactory
de l'exemple StackWidget
qui montre des parties de la méthode onCreate()
:
Kotlin
private const val REMOTE_VIEW_COUNT: Int = 10 class StackRemoteViewsFactory( private val context: Context ) : RemoteViewsService.RemoteViewsFactory { private lateinit var widgetItems: List<WidgetItem> override fun onCreate() { // In onCreate(), set up any connections or cursors to your data // source. Heavy lifting, such as downloading or creating content, // must be deferred to onDataSetChanged() or getViewAt(). Taking // more than 20 seconds on this call results in an ANR. widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") } ... } ... }
Java
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int REMOTE_VIEW_COUNT = 10; private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>(); public void onCreate() { // In onCreate(), setup any connections or cursors to your data // source. Heavy lifting, such as downloading or creating content, // must be deferred to onDataSetChanged() or getViewAt(). Taking // more than 20 seconds on this call results in an ANR. for (int i = 0; i < REMOTE_VIEW_COUNT; i++) { widgetItems.add(new WidgetItem(i + "!")); } ... } ...
La méthode RemoteViewsFactory
getViewAt()
renvoie un objet RemoteViews
correspondant aux données au position
spécifié dans l'ensemble de données. Voici un extrait de l'implémentation RemoteViewsFactory
de l'exemple StackWidget
:
Kotlin
override fun getViewAt(position: Int): RemoteViews { // Construct a remote views item based on the widget item XML file // and set the text based on the position. return RemoteViews(context.packageName, R.layout.widget_item).apply { setTextViewText(R.id.widget_item, widgetItems[position].text) } }
Java
public RemoteViews getViewAt(int position) { // Construct a remote views item based on the widget item XML file // and set the text based on the position. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_item); views.setTextViewText(R.id.widget_item, widgetItems.get(position).text); return views; }
Ajouter un comportement à des éléments individuels
Les sections précédentes montrent comment lier vos données à votre collection de widgets. Mais que se passe-t-il si vous souhaitez ajouter un comportement dynamique aux éléments individuels de votre vue de collection ?
Comme décrit dans la section Gérer les événements avec la classe onUpdate()
, vous utilisez normalement setOnClickPendingIntent()
pour définir le comportement de clic d'un objet, par exemple pour qu'un bouton lance un Activity
. Toutefois, cette approche n'est pas autorisée pour les vues enfants d'un élément de collection individuel.
Par exemple, vous pouvez utiliser setOnClickPendingIntent()
pour configurer un bouton global dans le widget Gmail qui lance l'application, par exemple, mais pas sur les éléments de liste individuels.
Pour ajouter un comportement de clic à des éléments individuels d'une collection, utilisez plutôt setOnClickFillInIntent()
. Pour ce faire, configurez un modèle d'intent en attente pour votre vue de collection, puis définissez un intent de remplissage sur chaque élément de la collection via votre RemoteViewsFactory
.
Cette section utilise l'exemple StackWidget
pour décrire comment ajouter un comportement à des éléments individuels. Dans l'exemple StackWidget
, si l'utilisateur appuie sur la vue supérieure, le widget affiche le message Toast
"Vue touchée n", où n est l'indice (position) de la vue touchée. Voici comment cela fonctionne :
StackWidgetProvider
(une sous-classeAppWidgetProvider
) crée un intent en attente avec une action personnalisée appeléeTOAST_ACTION
.Lorsque l'utilisateur appuie sur une vue, l'intent se déclenche et diffuse
TOAST_ACTION
.Cette diffusion est interceptée par la méthode
onReceive()
de la classeStackWidgetProvider
, et le widget affiche le messageToast
pour la vue touchée. Les données des éléments de la collection sont fournies parRemoteViewsFactory
viaRemoteViewsService
.
Configurer le modèle d'intent en attente
StackWidgetProvider
(une sous-classe AppWidgetProvider
) configure un intent en attente. Les éléments individuels d'une collection ne peuvent pas configurer leurs propres intents en attente. Au lieu de cela, la collection dans son ensemble configure un modèle d'intent en attente, et les éléments individuels définissent un intent de remplissage pour créer un comportement unique par élément.
Cette classe reçoit également la diffusion envoyée lorsque l'utilisateur appuie sur une vue. Il traite cet événement dans sa méthode onReceive()
. Si l'action de l'intent est TOAST_ACTION
, le widget affiche un message Toast
pour la vue actuelle.
Kotlin
const val TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION" const val EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM" class StackWidgetProvider : AppWidgetProvider() { ... // Called when the BroadcastReceiver receives an Intent broadcast. // Checks whether the intent's action is TOAST_ACTION. If it is, the // widget displays a Toast message for the current item. override fun onReceive(context: Context, intent: Intent) { val mgr: AppWidgetManager = AppWidgetManager.getInstance(context) if (intent.action == TOAST_ACTION) { val appWidgetId: Int = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID ) // EXTRA_ITEM represents a custom value provided by the Intent // passed to the setOnClickFillInIntent() method to indicate the // position of the clicked item. See StackRemoteViewsFactory in // Set the fill-in Intent for details. val viewIndex: Int = intent.getIntExtra(EXTRA_ITEM, 0) Toast.makeText(context, "Touched view $viewIndex", Toast.LENGTH_SHORT).show() } super.onReceive(context, intent) } override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { // Update each of the widgets with the remote adapter. appWidgetIds.forEach { appWidgetId -> // Sets up the intent that points to the StackViewService that // provides the views for this collection. val intent = Intent(context, StackWidgetService::class.java).apply { putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) // When intents are compared, the extras are ignored, so embed // the extra sinto the data so that the extras are not ignored. data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) } val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply { setRemoteAdapter(R.id.stack_view, intent) // The empty view is displayed when the collection has no items. // It must be a sibling of the collection view. setEmptyView(R.id.stack_view, R.id.empty_view) } // This section makes it possible for items to have individualized // behavior. It does this by setting up a pending intent template. // Individuals items of a collection can't set up their own pending // intents. Instead, the collection as a whole sets up a pending // intent template, and the individual items set a fillInIntent // to create unique behavior on an item-by-item basis. val toastPendingIntent: PendingIntent = Intent( context, StackWidgetProvider::class.java ).run { // Set the action for the intent. // When the user touches a particular view, it has the effect of // broadcasting TOAST_ACTION. action = TOAST_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT) } rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent) appWidgetManager.updateAppWidget(appWidgetId, rv) } super.onUpdate(context, appWidgetManager, appWidgetIds) } }
Java
public class StackWidgetProvider extends AppWidgetProvider { public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"; public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"; ... // Called when the BroadcastReceiver receives an Intent broadcast. // Checks whether the intent's action is TOAST_ACTION. If it is, the // widget displays a Toast message for the current item. @Override public void onReceive(Context context, Intent intent) { AppWidgetManager mgr = AppWidgetManager.getInstance(context); if (intent.getAction().equals(TOAST_ACTION)) { int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); // EXTRA_ITEM represents a custom value provided by the Intent // passed to the setOnClickFillInIntent() method to indicate the // position of the clicked item. See StackRemoteViewsFactory in // Set the fill-in Intent for details. int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0); Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show(); } super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Update each of the widgets with the remote adapter. for (int i = 0; i < appWidgetIds.length; ++i) { // Sets up the intent that points to the StackViewService that // provides the views for this collection. Intent intent = new Intent(context, StackWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); // When intents are compared, the extras are ignored, so embed // the extras into the data so that the extras are not // ignored. intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent); // The empty view is displayed when the collection has no items. It // must be a sibling of the collection view. rv.setEmptyView(R.id.stack_view, R.id.empty_view); // This section makes it possible for items to have individualized // behavior. It does this by setting up a pending intent template. // Individuals items of a collection can't set up their own pending // intents. Instead, the collection as a whole sets up a pending // intent template, and the individual items set a fillInIntent // to create unique behavior on an item-by-item basis. Intent toastIntent = new Intent(context, StackWidgetProvider.class); // Set the action for the intent. // When the user touches a particular view, it has the effect of // broadcasting TOAST_ACTION. toastIntent.setAction(StackWidgetProvider.TOAST_ACTION); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } super.onUpdate(context, appWidgetManager, appWidgetIds); } }
Définir l'intent de saisie
Votre RemoteViewsFactory
doit définir un intent de saisie sur chaque élément de la collection. Cela permet de distinguer l'action individuelle sur un clic d'un élément donné. L'intent de saisie est ensuite combiné au modèle PendingIntent
pour déterminer l'intent final exécuté lorsque l'utilisateur appuie sur l'élément.
Kotlin
private const val REMOTE_VIEW_COUNT: Int = 10 class StackRemoteViewsFactory( private val context: Context, intent: Intent ) : RemoteViewsService.RemoteViewsFactory { private lateinit var widgetItems: List<WidgetItem> private val appWidgetId: Int = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID ) override fun onCreate() { // In onCreate(), set up any connections or cursors to your data source. // Heavy lifting, such as downloading or creating content, must be // deferred to onDataSetChanged() or getViewAt(). Taking more than 20 // seconds on this call results in an ANR. widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") } ... } ... override fun getViewAt(position: Int): RemoteViews { // Construct a remote views item based on the widget item XML file // and set the text based on the position. return RemoteViews(context.packageName, R.layout.widget_item).apply { setTextViewText(R.id.widget_item, widgetItems[position].text) // Set a fill-intent to fill in the pending intent template. // that is set on the collection view in StackWidgetProvider. val fillInIntent = Intent().apply { Bundle().also { extras -> extras.putInt(EXTRA_ITEM, position) putExtras(extras) } } // Make it possible to distinguish the individual on-click // action of a given item. setOnClickFillInIntent(R.id.widget_item, fillInIntent) ... } } ... }
Java
public class StackWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int count = 10; private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>(); private Context context; private int appWidgetId; public StackRemoteViewsFactory(Context context, Intent intent) { this.context = context; appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } // Initialize the data set. public void onCreate() { // In onCreate(), set up any connections or cursors to your data // source. Heavy lifting, such as downloading or creating // content, must be deferred to onDataSetChanged() or // getViewAt(). Taking more than 20 seconds on this call results // in an ANR. for (int i = 0; i < count; i++) { widgetItems.add(new WidgetItem(i + "!")); } ... } // Given the position (index) of a WidgetItem in the array, use the // item's text value in combination with the widget item XML file to // construct a RemoteViews object. public RemoteViews getViewAt(int position) { // Position always ranges from 0 to getCount() - 1. // Construct a RemoteViews item based on the widget item XML // file and set the text based on the position. RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item); rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text); // Set a fill-intent to fill in the pending // intent template that is set on the collection view in // StackWidgetProvider. Bundle extras = new Bundle(); extras.putInt(StackWidgetProvider.EXTRA_ITEM, position); Intent fillInIntent = new Intent(); fillInIntent.putExtras(extras); // Make it possible to distinguish the individual on-click // action of a given item. rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); // Return the RemoteViews object. return rv; } ... }
Actualiser les données de collecte
La figure 2 illustre le flux de mise à jour dans un widget qui utilise des collections. Il montre comment le code du widget interagit avec RemoteViewsFactory
et comment vous pouvez déclencher des mises à jour:
Les widgets qui utilisent des collections peuvent fournir aux utilisateurs des contenus à jour. Par exemple, le widget Gmail donne aux utilisateurs un aperçu de leur boîte de réception. Pour ce faire, déclenchez votre RemoteViewsFactory
et votre vue de collection pour extraire et afficher de nouvelles données.
Pour ce faire, utilisez AppWidgetManager
pour appeler notifyAppWidgetViewDataChanged()
. Cet appel génère un rappel de la méthode onDataSetChanged()
de votre objet RemoteViewsFactory
, qui vous permet d'extraire de nouvelles données.
Vous pouvez effectuer des opérations de traitement intensives de manière synchrone dans le rappel onDataSetChanged()
. Vous êtes assuré que cet appel est terminé avant que les métadonnées ou les données de vue ne soient extraites de RemoteViewsFactory
. Vous pouvez également effectuer des opérations de traitement intensives dans la méthode getViewAt()
. Si cet appel prend du temps, la vue de chargement (spécifiée par la méthode getLoadingView()
de l'objet RemoteViewsFactory
) s'affiche à la position correspondante de la vue de collection jusqu'à ce qu'il renvoie une valeur.
Utiliser RemoteCollectionItems pour transmettre directement une collection
Android 12 (niveau d'API 31) ajoute la méthode setRemoteAdapter(int viewId,
RemoteViews.RemoteCollectionItems
items)
, qui permet à votre application de transmettre directement une collection lors du remplissage d'une vue de collection. Si vous configurez votre adaptateur à l'aide de cette méthode, vous n'avez pas besoin d'implémenter un RemoteViewsFactory
et vous n'avez pas besoin d'appeler notifyAppWidgetViewDataChanged()
.
En plus de faciliter le remplissage de votre adaptateur, cette approche supprime également la latence de remplissage des nouveaux éléments lorsque les utilisateurs font défiler la liste pour afficher un nouvel élément. Cette approche de configuration de l'adaptateur est à privilégier tant que votre ensemble d'éléments de collection est relativement petit. Toutefois, par exemple, cette approche ne fonctionne pas bien si votre collection contient de nombreux Bitmaps
transmis à setImageViewBitmap
.
Si la collection n'utilise pas un ensemble constant de mises en page (c'est-à-dire si certains éléments ne sont présents que parfois), utilisez setViewTypeCount
pour spécifier le nombre maximal de mises en page uniques que la collection peut contenir. Cela permet de réutiliser l'adaptateur lors des mises à jour de votre widget d'application.
Voici un exemple d'implémentation de collections RemoteViews
simplifiées.
Kotlin
val itemLayouts = listOf( R.layout.item_type_1, R.layout.item_type_2, ... ) remoteView.setRemoteAdapter( R.id.list_view, RemoteViews.RemoteCollectionItems.Builder() .addItem(/* id= */ ID_1, RemoteViews(context.packageName, R.layout.item_type_1)) .addItem(/* id= */ ID_2, RemoteViews(context.packageName, R.layout.item_type_2)) ... .setViewTypeCount(itemLayouts.count()) .build() )
Java
List<Integer> itemLayouts = Arrays.asList( R.layout.item_type_1, R.layout.item_type_2, ... ); remoteView.setRemoteAdapter( R.id.list_view, new RemoteViews.RemoteCollectionItems.Builder() .addItem(/* id= */ ID_1, new RemoteViews(context.getPackageName(), R.layout.item_type_1)) .addItem(/* id= */ ID_2, new RemoteViews(context.getPackageName(), R.layout.item_type_2)) ... .setViewTypeCount(itemLayouts.size()) .build() );