使用集合微件

集合微件专门用于显示多个相同类型的元素,例如 作为图库应用中的图片集合、新闻应用中的文章,或 从通信应用获取消息集合 widget 通常侧重于两种用途 案例:浏览集合并打开集合的某个元素, 详情视图。集合微件可以垂直滚动。

这些微件使用 要显示的内容数量:RemoteViewsService 由远程数据(如来自 provider。该微件会显示 数据使用以下视图类型之一,这些视图类型称为集合 观看次数

ListView
以视图形式显示内容 垂直滚动列表。
GridView
以视图形式显示内容 二维滚动网格。
StackView
一张堆叠的卡片 分别查看上一张或下一张卡片。
AdapterViewFlipper
适配器支持简单 具有动画效果的 ViewAnimator 在两个或更多视图之间来回切换一次只能显示一个子级。

由于这些集合视图显示由远程数据支持的集合, 使用 Adapter 绑定其用户 与其数据的接口Adapter 绑定了一组数据中的各个项 单个 View 对象。

由于这些集合视图由适配器提供支持,因此 Android 框架 必须包含额外的架构来支持在 widget 中使用它们。在上下文中 时,Adapter 会替换为 RemoteViewsFactory, 它是 Adapter 接口的瘦封装容器。收到请求后, 集合中的特定项时,RemoteViewsFactory 会创建并返回 作为集合中的项 RemoteViews 对象。要加入 在 widget 中实现集合视图,实现 RemoteViewsServiceRemoteViewsFactory

RemoteViewsService 是一项服务,可让遥控器适配器请求 RemoteViews 对象。RemoteViewsFactory 是适配器的接口 (例如 ListViewGridViewStackView)以及该视图的底层数据。来源:StackWidget 示例, 这是实现此服务的样板代码示例 接口:

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.

}

示例应用

本部分中的代码段也从 StackWidget 示例

图 1. 一个 StackWidget

此示例包含 10 个视图的堆栈,这些视图显示值 0 一直到九点为止示例 widget 具有以下主要行为:

  • 用户可以垂直滑动 widget 中的顶部视图,以显示下一个 还是上一视图这是内置的 StackView 行为。

  • 在没有任何用户互动的情况下,该微件会自动推进到 按顺序观看,就像幻灯片一样。这是因为 android:autoAdvanceViewId="@id/stack_view"res/xml/stackwidgetinfo.xml 文件。此设置适用于数据视图 ID 在本例中为堆栈视图的视图 ID。

  • 如果用户轻触顶部视图,该微件会显示 Toast 消息“触摸视图 n”,其中 n 是触摸视图的索引(位置)。想要详细了解 请参阅为单个用户添加行为 items 部分。

通过集合实现 widget

要实现包含集合的微件,请按照在 widget,然后执行几个额外的步骤: 修改清单,向微件布局添加集合视图, AppWidgetProvider 子类。

包含集合的微件的清单

除了在 清单,您需要让 绑定到您的RemoteViewsService。为此,请将 具有 此权限的清单文件 BIND_REMOTEVIEWS。 这可以防止其他应用自由访问微件的数据。

例如,创建一个使用 RemoteViewsService 填充 集合视图中,清单条目可能如下所示:

<service android:name="MyWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

在此示例中,android:name="MyWidgetService" 引用了 RemoteViewsService

包含集合的 widget 的布局

微件布局 XML 文件的主要要求是它必须包含 集合视图:ListViewGridViewStackViewAdapterViewFlipper。以下是 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>

请注意,空视图必须是 空视图表示空状态。

除了整个 widget 的布局文件之外,还要创建另一个布局 文件,用于定义集合中每一项的布局,例如, 为图书集合中的每本图书设置布局。StackWidget 示例具有 只有一个项布局文件 widget_item.xml,因为所有项都使用相同的 布局。

适用于包含集合的微件的 AppWidgetProvider 类

与常规微件一样, AppWidgetProvider 子类 通常会 onUpdate()。 在创建onUpdate() 具有集合的微件是您必须调用 setRemoteAdapter()。这会告知集合视图从何处获取其数据。 然后,RemoteViewsService 可以返回您对 RemoteViewsFactory,并且 widget 可以提供适当的数据。当您 调用此方法,然后传递一个指向您的 RemoteViewsService 和指定要更新的 widget 的 ID。

以下示例展示了 StackWidget 示例如何实现 onUpdate()RemoteViewsService 设置为 微件集合:

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);
}

保留数据

如本页所述,RemoteViewsService 子类提供 用于填充远程集合视图的 RemoteViewsFactory

具体来说,请执行以下步骤:

  1. RemoteViewsService 子类。RemoteViewsService 是通过 远程适配器可以请求 RemoteViews

  2. RemoteViewsService 子类中,添加一个用于实现 RemoteViewsFactory 接口。RemoteViewsFactory 是 适配器视图之间的适配器,例如 ListViewGridViewStackView 以及该视图的底层数据。您的 实现负责为每个容器创建一个 RemoteViews 对象 数据集中。此接口是 Adapter 的瘦封装容器。

您不得依赖服务的单个实例或其包含的任何数据 持久保留。除非是静态的,否则不要将数据存储在 RemoteViewsService 中。如果 如果您想保留 widget 的数据,最好的方法是使用 ContentProvider,其数据 会持续超过进程生命周期例如,杂货店微件可以 将每个购物清单项的状态存储在一个永久性位置,例如 SQL 数据库。

RemoteViewsService 实现的主要内容是 RemoteViewsFactory(如下一部分中所述)。

RemoteViewsFactory 接口

实现 RemoteViewsFactory 接口的自定义类提供了以下功能: 具有其集合中项的数据的 widget。为此, 将 widget 项 XML 布局文件与数据源合并。此来源 数据可以是数据库、简单数组等任何内容。在StackWidget中 则该数据源为 WidgetItems 的数组。RemoteViewsFactory 用作适配器,将数据粘合到远程集合视图中。

您需要为自己的代码实现的两个最重要的方法 RemoteViewsFactory 子类是 onCreate()getViewAt()

首次创建工厂时,系统会调用 onCreate()。 您可以在此处设置与数据源的任何连接或游标。对于 例如,StackWidget 示例使用 onCreate() 来初始化 WidgetItem 对象。当微件处于活动状态时,系统会访问这些 对象使用其在数组中的索引位置,并显示相应的文本。 包含。

以下是摘录自StackWidget示例的RemoteViewsFactory 显示 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 + "!"));
        }
        ...
    }
...

RemoteViewsFactory 方法 getViewAt() 会返回 RemoteViews 对象 对应于数据集中指定 position 的数据。以下是 摘录自 StackWidget 示例的 RemoteViewsFactory 实现:

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;
}

为各个项添加行为

前面的部分介绍了如何将数据绑定到 widget 集合。但是 如果您希望为广告系列中的各个项目添加动态行为, 集合视图?

使用 onUpdate() 处理事件中所述 类,通常使用 setOnClickPendingIntent(),用于设置对象的点击行为,例如 使按钮启动 Activity。但是 不允许对单个集合项中的子视图使用此方法。 例如,您可以使用 setOnClickPendingIntent() 设置全局按钮 例如,在用于启动应用的 Gmail 小组件中,而不是在 。

而是要向集合中的各个项添加点击行为,请使用 setOnClickFillInIntent()。这需要设置待处理 intent 模板, 然后在集合视图中 通过RemoteViewsFactory收集。

本部分使用 StackWidget 示例来说明如何将行为添加到 单个项。在 StackWidget 示例中,如果用户轻触顶部视图, 该 widget 会显示 Toast 消息“Touched view n”,其中,n 是 被触摸视图的索引(位置)。其工作原理如下:

  • StackWidgetProvider - AppWidgetProvider 子类 - 使用名为 TOAST_ACTION

  • 当用户触摸视图时,会触发 intent 并广播 TOAST_ACTION

  • 此广播会被 StackWidgetProvider 类的 onReceive() 方法,并且该 widget 会显示 Toast 消息 使用触摸视图合集项的数据由 RemoteViewsFactoryRemoteViewsService

设置待处理 intent 模板

StackWidgetProviderAppWidgetProvider 子类) 设置待定 intent集合中的个别项无法设置其 自己的待处理 intent。相反,整个集合会设置一个待定 intent 各个项设置填充 intent 来创建唯一 对每件商品的行为进行针对性的处理。

该类还会接收在用户触摸 视图。它在自己的 onReceive() 方法中处理此事件。如果意图的 操作为 TOAST_ACTION 时,该 widget 会针对当前的Toast 视图。

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);
    }
}

设置填充 intent

您的 RemoteViewsFactory 必须在 。这样就可以区分每个点击操作 属性。填充 intent 随后会与 PendingIntent 模板以确定 点按项目时执行的最终 intent。

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;
    }
    ...
}

使集合数据保持最新状态

图 2 展示了使用集合的 widget 中的更新流程。它会显示 widget 代码如何与 RemoteViewsFactory 交互,以及如何 触发器更新:

图 2. 在更新期间与 RemoteViewsFactory 互动。

使用集合的微件可以为用户提供最新内容。对于 例如,Gmail 微件可为用户提供收件箱的快照。要使 触发 RemoteViewsFactory 和集合视图来获取和 显示新数据。

为此,请使用 AppWidgetManager 可呼叫 notifyAppWidgetViewDataChanged()。此调用会导致对 RemoteViewsFactory 对象的 onDataSetChanged() 方法,这样您便可以提取任何新数据。

您可以在 onDataSetChanged() 回调。我们保证会完成此次通话 然后再从 RemoteViewsFactory 提取元数据或视图数据。您 还可以在 getViewAt() 内执行处理密集型操作, 方法。如果此调用需要很长时间,则加载视图(由 RemoteViewsFactory 对象的 getLoadingView() 方法 - 显示在集合视图的相应位置 直到返回结果。

使用 RemoteCollectionItems 直接传递集合

Android 12(API 级别 31)添加了 setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) 方法,这样您的应用就可以在填充 集合视图如果您使用此方法设置适配器,则无需 实现 RemoteViewsFactory,并且无需调用 notifyAppWidgetViewDataChanged()

除了让您更轻松地填充适配器外,此方法还可 消除了在用户向下滚动列表以填充新项时出现的延迟 显示一个新项只要满足以下条件,最好使用这种方法设置适配器 您的集合项集合相对较小。不过,举个例子, 如果您的集合包含大量 Bitmaps,则此方法将不太奏效 传递给 setImageViewBitmap

如果集合没有使用一组固定的布局(也就是说, 项只是偶尔存在,请使用 setViewTypeCount 指定 集合可以包含的唯一布局的最大数量。这样, 适配器在应用微件的更新间重复使用。

以下示例说明了如何实现简化的 RemoteViews 集合。

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()
);