Виджеты-коллекции специализируются на отображении множества элементов одного типа, например, подборок изображений из приложения-галереи, статей из новостного приложения или сообщений из коммуникационного приложения. Виджеты-коллекции обычно предназначены для двух вариантов использования: просмотра коллекции и открытия элемента коллекции для просмотра его подробностей. Виджеты-коллекции поддерживают вертикальную прокрутку.
Эти виджеты используют RemoteViewsService
для отображения коллекций, основанных на удалённых данных, например, от поставщика контента . Виджет представляет данные с помощью одного из следующих типов представлений, известных как представления коллекций :
-
ListView
- Представление, в котором элементы отображаются в виде вертикально прокручиваемого списка.
-
GridView
- Представление, отображающее элементы в виде двухмерной прокручиваемой сетки.
-
StackView
- Сложенный вид карточек — что-то вроде картотеки, — где пользователь может перелистнуть первую карточку вверх или вниз, чтобы увидеть предыдущую или следующую карточку соответственно.
-
AdapterViewFlipper
- Простой
ViewAnimator
с поддержкой адаптера, который анимирует между двумя или более представлениями. Одновременно отображается только один дочерний элемент.
Поскольку эти представления-коллекции отображают коллекции, основанные на удалённых данных, они используют Adapter
для привязки своего пользовательского интерфейса к данным. Adapter
привязывает отдельные элементы из набора данных к отдельным объектам View
.
А поскольку эти представления-коллекции поддерживаются адаптерами, фреймворк Android должен включать дополнительную архитектуру для поддержки их использования в виджетах. В контексте виджета Adapter
заменяется RemoteViewsFactory
— тонкой оберткой вокруг интерфейса Adapter
. При запросе конкретного элемента коллекции RemoteViewsFactory
создаёт и возвращает элемент коллекции в виде объекта RemoteViews
. Чтобы включить представление-коллекцию в виджет, реализуйте RemoteViewsService
и RemoteViewsFactory
.
RemoteViewsService
— это служба, позволяющая удалённому адаптеру запрашивать объекты RemoteViews
. RemoteViewsFactory
— это интерфейс для адаптера между представлением коллекции, таким как ListView
, GridView
и StackView
, и базовыми данными для этого представления. Вот пример шаблонного кода для реализации этой службы и интерфейса из примера StackWidget
:
Котлин
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. }
Ява
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. }
Образец заявления
Фрагменты кода в этом разделе также взяты из примера StackWidget
:

StackWidget
.Этот пример состоит из десяти представлений, отображающих значения от нуля до девяти. Этот пример виджета имеет следующие основные функции:
Пользователь может переместить верхнюю часть виджета вертикально, чтобы отобразить следующий или предыдущий вид. Это встроенное поведение
StackView
.Виджет автоматически, без взаимодействия с пользователем, последовательно переключает свои представления, как в слайд-шоу. Это происходит благодаря настройке
android:autoAdvanceViewId="@id/stack_view"
в файлеres/xml/stackwidgetinfo.xml
. Эта настройка применяется к идентификатору представления, который в данном случае является идентификатором представления в стеке.Если пользователь касается верхней панели, виджет отображает
Toast
сообщение «Touched view n », где n — индекс (позиция) затронутой панели. Подробнее о реализации поведений см. в разделе «Добавление поведения к отдельным элементам» .
Реализуйте виджеты с коллекциями
Чтобы реализовать виджет с коллекциями, следуйте процедуре реализации любого виджета , а затем выполните несколько дополнительных шагов: измените манифест, добавьте представление коллекции в макет виджета и измените подкласс AppWidgetProvider
.
Манифест для виджетов с коллекциями
Помимо требований, перечисленных в разделе «Объявление виджета в манифесте» , необходимо обеспечить возможность привязки виджетов с коллекциями к вашему RemoteViewsService
. Для этого необходимо объявить службу в файле манифеста с разрешением BIND_REMOTEVIEWS
. Это предотвратит свободный доступ других приложений к данным вашего виджета.
Например, при создании виджета, который использует RemoteViewsService
для заполнения представления коллекции, запись манифеста может выглядеть следующим образом:
<service android:name="MyWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
В этом примере android:name="MyWidgetService"
ссылается на ваш подкласс RemoteViewsService
.
Макет виджетов с коллекциями
Главное требование к XML-файлу макета виджета — он должен включать одно из представлений коллекции: ListView
, GridView
, StackView
или AdapterViewFlipper
. Вот файл widget_layout.xml
для примера 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>
Обратите внимание, что пустые представления должны быть родственными представлениям представления коллекции, для которого пустое представление представляет пустое состояние.
В дополнение к файлу макета для всего виджета создайте ещё один файл макета, который определяет макет каждого элемента в коллекции, например, макет каждой книги в коллекции. В примере StackWidget
есть только один файл макета элемента, widget_item.xml
, поскольку все элементы используют один и тот же макет.
Класс AppWidgetProvider для виджетов с коллекциями
Как и в случае с обычными виджетами, большая часть кода в подклассе AppWidgetProvider
обычно находится в onUpdate()
. Главное отличие в реализации onUpdate()
при создании виджета с коллекциями заключается в необходимости вызова setRemoteAdapter()
. Это сообщает представлению коллекции, откуда брать данные. После этого RemoteViewsService
может вернуть вашу реализацию RemoteViewsFactory
, и виджет сможет предоставить соответствующие данные. При вызове этого метода передайте намерение, указывающее на вашу реализацию RemoteViewsService
, и идентификатор виджета, который нужно обновить.
Например, вот как пример StackWidget
реализует метод обратного вызова onUpdate()
для установки RemoteViewsService
в качестве удаленного адаптера для коллекции виджетов:
Котлин
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) }
Ява
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); }
Сохранение данных
Как описано на этой странице, подкласс RemoteViewsService
предоставляет RemoteViewsFactory
, используемый для заполнения представления удаленной коллекции.
В частности, выполните следующие действия:
Подкласс
RemoteViewsService
.RemoteViewsService
— это служба, через которую удаленный адаптер может запрашиватьRemoteViews
.В подклассе
RemoteViewsService
включите класс, реализующий интерфейсRemoteViewsFactory
.RemoteViewsFactory
— это интерфейс для адаптера между удалённым представлением коллекции, таким какListView
,GridView
,StackView
, и базовыми данными для этого представления. Ваша реализация отвечает за создание объектаRemoteViews
для каждого элемента в наборе данных. Этот интерфейс представляет собой тонкую оболочку вокругAdapter
.
Вы не можете полагаться на сохранение одного экземпляра вашей службы или любых содержащихся в ней данных. Не храните данные в RemoteViewsService
, если они не являются статическими. Если вы хотите, чтобы данные вашего виджета сохранялись, лучшим решением будет использовать ContentProvider
, данные которого сохраняются и после окончания жизненного цикла процесса. Например, виджет продуктового магазина может хранить состояние каждого элемента списка покупок в постоянном месте, например, в базе данных SQL.
Основным содержимым реализации RemoteViewsService
является RemoteViewsFactory
, описанный в следующем разделе.
Интерфейс RemoteViewsFactory
Ваш пользовательский класс, реализующий интерфейс RemoteViewsFactory
, предоставляет виджету данные об элементах его коллекции. Для этого он объединяет XML-файл макета элементов виджета с источником данных. Этот источник данных может быть чем угодно: от базы данных до простого массива. В примере StackWidget
источником данных является массив WidgetItems
. RemoteViewsFactory
выступает в качестве адаптера для присоединения данных к представлению удалённой коллекции.
Два наиболее важных метода, которые необходимо реализовать для подкласса RemoteViewsFactory
, — это onCreate()
и getViewAt()
.
Система вызывает onCreate()
при первом создании фабрики. Здесь вы настраиваете все подключения или курсоры к источнику данных. Например, в примере StackWidget
метод onCreate()
используется для инициализации массива объектов WidgetItem
. Когда виджет активен, система обращается к этим объектам, используя их индекс в массиве, и отображает содержащийся в них текст.
Вот отрывок из реализации RemoteViewsFactory
примера StackWidget
, который показывает части метода onCreate()
:
Котлин
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!") } ... } ... }
Ява
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 + "!")); } ... } ...
Метод RemoteViewsFactory
getViewAt()
возвращает объект RemoteViews
, соответствующий данным в указанной position
в наборе данных. Вот фрагмент реализации RemoteViewsFactory
в примере StackWidget
:
Котлин
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) } }
Ява
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; }
Добавить поведение к отдельным элементам
В предыдущих разделах показано, как привязать данные к коллекции виджетов. Но что делать, если вы хотите добавить динамическое поведение к отдельным элементам в представлении коллекции?
Как описано в разделе Обработка событий с помощью класса onUpdate()
, метод setOnClickPendingIntent()
обычно используется для настройки поведения объекта при нажатии, например, для запуска Activity
при нажатии кнопки. Однако такой подход недопустим для дочерних представлений в отдельных элементах коллекции. Например, setOnClickPendingIntent()
можно использовать для настройки глобальной кнопки в виджете Gmail, которая запускает, например, само приложение, но не отдельные элементы списка.
Вместо этого, чтобы добавить поведение щелчка к отдельным элементам коллекции, используйте setOnClickFillInIntent()
. Это подразумевает настройку шаблона отложенного намерения для представления коллекции, а затем установку намерения заполнения для каждого элемента коллекции через RemoteViewsFactory
.
В этом разделе пример StackWidget
описывает, как добавить поведение к отдельным элементам. В примере StackWidget
, если пользователь касается верхней панели, виджет отображает Toast
сообщение «Touched view n », где n — индекс (позиция) затронутой панели. Вот как это работает:
StackWidgetProvider
— подклассAppWidgetProvider
— создает отложенное намерение с пользовательским действием, называемымTOAST_ACTION
.Когда пользователь касается представления, срабатывает намерение и передается
TOAST_ACTION
.Эта трансляция перехватывается методом
onReceive()
классаStackWidgetProvider
, и виджет отображаетToast
сообщение для выбранного представления. Данные об элементах коллекции предоставляютсяRemoteViewsFactory
черезRemoteViewsService
.
Настройте шаблон ожидающего намерения
StackWidgetProvider
(подкласс AppWidgetProvider
) устанавливает ожидающее намерение. Отдельные элементы коллекции не могут устанавливать собственные ожидающие намерения. Вместо этого коллекция в целом устанавливает шаблон ожидающего намерения, а отдельные элементы устанавливают заполняющее намерение для создания уникального поведения для каждого элемента.
Этот класс также получает широковещательное сообщение, отправляемое при касании пользователем представления. Он обрабатывает это событие в методе onReceive()
. Если действие намерения — TOAST_ACTION
, виджет отображает Toast
сообщение для текущего представления.
Котлин
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) } }
Ява
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); } }
Установите цель заполнения
Ваш RemoteViewsFactory
должен задать намерение заполнения для каждого элемента коллекции. Это позволяет различать отдельные действия по щелчку для каждого элемента. Затем намерение заполнения объединяется с шаблоном PendingIntent
для определения конечного намерения, которое выполняется при касании элемента.
Котлин
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) ... } } ... }
Ява
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; } ... }
Поддерживайте актуальность собираемых данных
На рисунке 2 показан процесс обновления виджета, использующего коллекции. Он показывает, как код виджета взаимодействует с RemoteViewsFactory
и как можно инициировать обновления:

RemoteViewsFactory
во время обновлений. Виджеты, использующие коллекции, могут предоставлять пользователям актуальный контент. Например, виджет Gmail предоставляет пользователям снимок их почтового ящика. Чтобы это стало возможным, активируйте RemoteViewsFactory
и представление коллекции для извлечения и отображения новых данных.
Для этого используйте AppWidgetManager
для вызова метода notifyAppWidgetViewDataChanged()
. Этот вызов приводит к обратному вызову метода onDataSetChanged()
объекта RemoteViewsFactory
, который позволяет получить любые новые данные.
Вы можете синхронно выполнять ресурсоёмкие операции в обратном вызове onDataSetChanged()
. Вы гарантированно завершите этот вызов до того, как метаданные или данные представления будут извлечены из RemoteViewsFactory
. Вы также можете выполнять ресурсоёмкие операции в методе getViewAt()
. Если этот вызов занимает много времени, представление загрузки, указанное методом getLoadingView()
объекта RemoteViewsFactory
, отображается в соответствующей позиции представления коллекции до тех пор, пока не будет выполнен возврат.
Используйте RemoteCollectionItems для прямой передачи коллекции
В Android 12 (API уровня 31) добавлен метод setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items)
, который позволяет приложению передавать коллекцию напрямую при заполнении представления коллекции. Если вы настраиваете адаптер с помощью этого метода, вам не нужно реализовывать RemoteViewsFactory
и вызывать notifyAppWidgetViewDataChanged()
.
Помимо упрощения заполнения адаптера, этот подход также устраняет задержку заполнения новых элементов, когда пользователи прокручивают список, чтобы найти новый элемент. Такой подход к настройке адаптера предпочтителен, если набор элементов коллекции относительно невелик. Однако, например, он не работает, если ваша коллекция содержит множество Bitmaps
передаваемых в setImageViewBitmap
.
Если коллекция не использует постоянный набор макетов (то есть некоторые элементы присутствуют лишь иногда), используйте setViewTypeCount
, чтобы указать максимальное количество уникальных макетов, которые может содержать коллекция. Это позволит повторно использовать адаптер при обновлениях виджета вашего приложения.
Вот пример реализации упрощенных коллекций RemoteViews
.
Котлин
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() )
Ява
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() );