集合 widget 专门用于显示许多相同类型的元素,例如来自图库应用的图片集合、来自新闻应用中的报道,或来自通信应用的消息。集合 widget 通常侧重于两种用例:浏览集合以及打开集合中的元素并将其打开到其详情视图。集合微件可以垂直滚动。
这些 widget 使用 RemoteViewsService
显示由远程数据(例如来自 content provider)支持的集合。该 widget 使用以下视图类型(称为“集合视图”)之一来呈现数据:
ListView
- 一种在垂直滚动列表中显示项目的视图。
GridView
- 一种在二维滚动网格中显示项目的视图。
StackView
- 一种堆叠的卡片视图(有点像 rolodex 文件),用户可以上下翻动正面卡片来查看上一张或下一张卡片。
AdapterViewFlipper
- 由适配器支持的简单
ViewAnimator
,可在两个或更多视图之间添加动画效果。一次只显示一个孩子。
由于这些集合视图会显示由远程数据支持的集合,因此它们使用 Adapter
将其界面绑定到其数据。Adapter
可将一组数据中的各个项绑定到各个 View
对象。
由于这些集合视图由适配器提供支持,因此 Android 框架必须包含额外的架构以支持在 widget 中使用它们。在 widget 的上下文中,Adapter
被 RemoteViewsFactory
取代,后者是 Adapter
接口的瘦封装容器。当您请求集合中的特定项时,RemoteViewsFactory
会为集合创建该项并将其作为 RemoteViews
对象返回。如需在 widget 中添加集合视图,请实现 RemoteViewsService
和 RemoteViewsFactory
。
RemoteViewsService
是一项允许远程适配器请求 RemoteViews
对象的服务。RemoteViewsFactory
是集合视图(如 ListView
、GridView
和 StackView
)与该视图的底层数据之间的适配器的接口。下面是实现此服务和接口的样板代码的示例(在 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
示例:
![](https://developer.android.google.cn/static/images/appwidgets/StackWidget.png?authuser=6&hl=sl)
StackWidget
。此示例由包含 10 个视图的堆栈组成,这些视图显示值 0 到 9。示例 widget 具有以下主要行为:
用户可以垂直滑动 widget 中的顶部视图,以显示下一个或上一个视图。这是内置的
StackView
行为。如果没有任何用户互动,widget 会自动按顺序播放其视图,就像幻灯片一样。这是因为在
res/xml/stackwidgetinfo.xml
文件中设置了android:autoAdvanceViewId="@id/stack_view"
。此设置适用于视图 ID,在本例中为堆栈视图的视图 ID。如果用户轻触顶部视图,该 widget 会显示
Toast
消息“Touched view n”,其中 n 是轻触的视图的索引(位置)。如需详细了解如何实现行为,请参阅向各个项添加行为部分。
使用集合实现 widget
如需实现包含集合的 widget,请按照实现任何 widget 的过程,然后执行几个额外步骤:修改清单,将集合视图添加到 widget 布局,以及修改 AppWidgetProvider
子类。
包含集合的 widget 的清单
除了在清单中声明 widget 中列出的要求之外,您还需要使具有集合的 widget 能够绑定到您的 RemoteViewsService
。为此,请在清单文件中使用 BIND_REMOTEVIEWS
权限声明该服务。这样可以防止其他应用自由访问微件的数据。
例如,在创建使用 RemoteViewsService
填充集合视图的 widget 时,清单条目可能如下所示:
<service android:name="MyWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
在此示例中,android:name="MyWidgetService"
引用了 RemoteViewsService
的子类。
包含集合的 widget 的布局
widget 布局 XML 文件的主要要求是它包含一个集合视图:ListView
、GridView
、StackView
或 AdapterViewFlipper
。以下是 StackWidget
示例的 widget_layout.xml
文件:
<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 类用于包含集合的 widget
与常规 widget 一样,AppWidgetProvider
子类中的大部分代码通常位于 onUpdate()
中。创建包含集合的 widget 时,在实现 onUpdate()
方面的主要区别在于,您必须调用 setRemoteAdapter()
。这会告知集合视图从何处获取其数据。然后,RemoteViewsService
可以返回 RemoteViewsFactory
的实现,并且 widget 可以提供相应的数据。当您调用此方法时,请传递一个指向您的 RemoteViewsService
实现的 intent,以及指定要更新的 widget 的 widget ID。
例如,以下代码展示了 StackWidget
示例如何实现 onUpdate()
回调方法,以将 RemoteViewsService
设置为 widget 集合的远程适配器:
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
。
具体而言,请执行以下步骤:
RemoteViewsService
子类。RemoteViewsService
是一项服务,远程适配器可以通过它来请求RemoteViews
。在
RemoteViewsService
子类中,添加一个实现RemoteViewsFactory
接口的类。RemoteViewsFactory
是远程集合视图(如ListView
、GridView
、StackView
)与该视图的底层数据之间的适配器的接口。您的实现负责为数据集中的每个项创建一个RemoteViews
对象。此接口是Adapter
的瘦封装容器。
您不能依赖服务的单个实例或其包含的任何数据持久保留。请勿将数据存储在 RemoteViewsService
中,除非它是静态的。如果您希望 widget 的数据保留下来,最好的方法是使用 ContentProvider
,其数据在进程生命周期结束后保留。例如,杂货店 widget 可以将每个购物清单项的状态存储在一个永久性位置,如 SQL 数据库。
RemoteViewsService
实现的主要内容是其 RemoteViewsFactory
,如以下部分所述。
RemoteViewsFactory 接口
实现 RemoteViewsFactory
接口的自定义类会为 widget 提供其集合中的项的数据。为此,它会将您的 widget 项 XML 布局文件与数据源相结合。此数据源可以是任何来源,可以是数据库,也可以是简单的数组。在 StackWidget
示例中,数据源是 WidgetItems
数组。RemoteViewsFactory
充当将数据粘附到远程集合视图的适配器。
您需要为 RemoteViewsFactory
子类实现的两个最重要的方法是 onCreate()
和 getViewAt()
。
首次创建工厂时,系统会调用 onCreate()
。您可以在此处设置指向数据源的任何连接或游标。例如,StackWidget
示例使用 onCreate()
来初始化 WidgetItem
对象的数组。当 widget 处于活动状态时,系统会使用这些对象在数组中的索引位置来访问这些对象,并显示它们包含的文本。
以下代码段摘录自 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 widget 中设置用于启动应用的全局按钮,但不能针对单个列表项设置该按钮。
如需向集合中的各个项添加点击行为,请改用 setOnClickFillInIntent()
。这需要为集合视图设置一个待处理 intent 模板,然后通过 RemoteViewsFactory
在集合中的每个项上设置填充 intent。
本部分使用 StackWidget
示例来说明如何为各个项添加行为。在 StackWidget
示例中,如果用户轻触顶部视图,widget 会显示 Toast
消息“Touched view n”,其中 n 是轻触的视图的索引(位置)。其工作原理如下:
StackWidgetProvider
(AppWidgetProvider
子类)会使用名为TOAST_ACTION
的自定义操作创建待定 intent。当用户轻触视图时,会触发 intent 并广播
TOAST_ACTION
。此广播会被
StackWidgetProvider
类的onReceive()
方法拦截,并且 widget 会针对触摸的视图显示Toast
消息。集合项的数据由RemoteViewsFactory
通过RemoteViewsService
提供。
设置待定 intent 模板
StackWidgetProvider
(AppWidgetProvider
子类)用于设置待定 intent。集合中的各个项无法设置自己的待处理 intent。相反,整个集合会设置一个待定 intent 模板,并且各个项会设置一个填充 intent,以逐个项创建独特的行为。
此类还会接收在用户触摸视图时发送的广播。它在自己的 onReceive()
方法中处理此事件。如果 intent 的操作是 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。这样即可区分给定项的各个点击操作。然后,填充 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 代码如何与 RemoteViewsFactory
交互以及如何触发更新:
![](https://developer.android.google.cn/static/images/appwidgets/appwidget_collections.png?authuser=6&hl=sl)
RemoteViewsFactory
的互动。使用集合的微件可以为用户提供最新内容。例如,Gmail widget 可以为用户提供收件箱的快照。为此,请触发 RemoteViewsFactory
和集合视图以获取和显示新数据。
为此,请使用 AppWidgetManager
调用 notifyAppWidgetViewDataChanged()
。此调用会回调 RemoteViewsFactory
对象的 onDataSetChanged()
方法,让您可以提取任何新数据。
您可以在 onDataSetChanged()
回调内同步执行处理密集型操作。可以保证,此调用在从 RemoteViewsFactory
提取元数据或视图数据之前完成。您还可以在 getViewAt()
方法中执行处理密集型操作。如果此调用用时较长,则加载视图(由 RemoteViewsFactory
对象的 getLoadingView()
方法指定)会显示在集合视图的相应位置,直到其返回为止。
使用 RemoteCollectionItems 直接传递集合
Android 12(API 级别 31)添加了 setRemoteAdapter(int viewId,
RemoteViews.RemoteCollectionItems
items)
方法,可让您的应用在填充集合视图时直接传递集合。如果您使用此方法设置适配器,则无需实现 RemoteViewsFactory
,也无需调用 notifyAppWidgetViewDataChanged()
。
除了更轻松地填充适配器之外,此方法还消除了当用户向下滚动列表以显示新项时填充新项的延迟。只要您的集合项集相对较小,就最好采用这种设置适配器的方法。但是,例如,如果您的集合包含大量传递给 setImageViewBitmap
的 Bitmaps
,则此方法就不太有效。
如果集合不使用一组常量布局(即,如果某些项有时只存在),请使用 setViewTypeCount
指定该集合可以包含的唯一布局数量上限。这样一来,适配器便可以在对应用 widget 的更新中重复使用。
以下示例说明了如何实现简化的 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() );