集合微件专门用于显示同一类型的多个元素,例如来自图库应用的照片集合、新闻应用的文章或通信应用的消息。集合微件通常专注于两个使用场景:浏览集合以及打开集合中的元素以查看其详细视图。集合微件可以垂直滚动。
这些微件使用 RemoteViewsService 来显示由远程数据(例如来自内容提供程序的数据)支持的集合。微件使用以下某种视图类型(称为集合视图)来呈现数据:
ListView- 一种在垂直滚动列表中显示项目的视图。
GridView- 一种在二维滚动网格中显示项目的视图。
StackView- 一种堆叠式卡片视图(类似于名片盒),用户可以向上或向下轻拂最前面的卡片,分别查看上一张或下一张卡片。
AdapterViewFlipper- 一种由适配器支持的简单
ViewAnimator,可在两个或多个视图之间进行动画切换。一次仅显示一个子项。
由于这些集合视图显示的是由远程数据支持的集合,因此它们使用 Adapter 将其用户界面绑定到其数据。 Adapter 将数据集中各个项目绑定到各个 View 对象。
并且由于这些集合视图由适配器支持,Android 框架必须包含额外的架构来支持它们在微件中的使用。在微件上下文中,Adapter 被 RemoteViewsFactory 替换,后者是对 Adapter 接口的一个轻量级封装。当请求集合中的特定项目时,RemoteViewsFactory 会创建该集合项目并将其作为 RemoteViews 对象返回。要在微件中包含集合视图,请实现 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 示例。
StackWidget。此示例由一堆显示 0 到 9 数值的十个视图组成。该示例微件具有以下主要行为:
用户可以垂直轻拂微件中的顶部视图来显示下一个或上一个视图。这是内置的
StackView行为。在没有任何用户交互的情况下,微件会自动按顺序切换其视图,就像幻灯片放映一样。这是由于
res/xml/stackwidgetinfo.xml文件中的设置android:autoAdvanceViewId="@id/stack_view"导致的。此设置应用于视图 ID,在此示例中即为堆叠视图的 ID。如果用户触摸顶部视图,微件会显示
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。这是 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>
请注意,空视图必须与它们所代表的空状态的集合视图处于同级位置。
除了整个微件的布局文件外,还需要创建另一个布局文件来定义集合中每个项目的布局,例如书籍集合中每本书的布局。StackWidget 示例只有一个项目布局文件 widget_item.xml,因为所有项目使用相同的布局。
带集合微件的 AppWidgetProvider 类
与常规微件一样,您的 AppWidgetProvider 子类中的大部分代码通常都在 onUpdate() 中。在创建带集合的微件时,onUpdate() 实现的主要区别在于您必须调用 setRemoteAdapter()。这会告知集合视图从何处获取数据。RemoteViewsService 随后可以返回您的 RemoteViewsFactory 实现,微件便可提供相应的数据。调用此方法时,请传入一个指向您的 RemoteViewsService 实现的 Intent,以及指定要更新的微件的微件 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。
具体而言,请执行以下步骤:
创建
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 对象数组。当您的微件处于活动状态时,系统会使用这些对象在数组中的索引位置来访问它们,并显示它们包含的文本。
以下是 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; }
为各个项目添加行为
前面的章节展示了如何将数据绑定到微件集合。但如果您想为集合视图中的各个项目添加动态行为该怎么办?
如 处理 onUpdate() 类中的事件中所述,您通常使用 setOnClickPendingIntent() 来设置对象的点击行为(例如使按钮启动 Activity)。但此方法不适用于单个集合项目中的子视图。例如,您可以使用 setOnClickPendingIntent() 为 Gmail 微件设置一个启动应用的全局按钮,但不能为各个列表项设置此行为。
相反,要为集合中的各个项目添加点击行为,请使用 setOnClickFillInIntent()。这需要为您的集合视图设置一个 PendingIntent 模板,然后通过您的 RemoteViewsFactory 在集合中的每个项目上设置一个填充 Intent (fill-in intent)。
本节使用 StackWidget 示例来描述如何为各个项目添加行为。在 StackWidget 示例中,如果用户触摸顶部视图,微件会显示 Toast 消息“Touched view n”,其中 n 是被触摸视图的索引(位置)。其工作原理如下:
StackWidgetProvider(一个AppWidgetProvider子类)创建一个带有自定义操作TOAST_ACTION的 PendingIntent。当用户触摸视图时,Intent 会触发并广播
TOAST_ACTION。此广播由
StackWidgetProvider类中的onReceive()方法拦截,微件随后会显示被触摸视图的Toast消息。集合项目的数据由RemoteViewsFactory通过RemoteViewsService提供。
设置 PendingIntent 模板
StackWidgetProvider(一个 AppWidgetProvider 子类)会设置一个 PendingIntent。集合中的各个项目无法设置自己的 PendingIntent。相反,整个集合会设置一个 PendingIntent 模板,各个项目则设置一个填充 Intent,以便逐个项目地创建独特的行为。
此类还会接收用户触摸视图时发送的广播。它会在其 onReceive() 方法中处理此事件。如果 Intent 的操作是 TOAST_ACTION,微件会显示当前视图的 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 展示了使用集合的微件中的更新流程。它展示了微件代码如何与 RemoteViewsFactory 交互,以及您如何触发更新:
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()。
除了使填充适配器更容易之外,此方法还消除了用户滚动列表以显示新项目时的延迟。只要您的集合项目数量相对较少,这种设置适配器的方法就是首选。但是,如果您的集合包含大量传递给 setImageViewBitmap 的 Bitmaps,则此方法效果不佳。
如果集合不使用固定的布局集(即某些项目仅有时出现),请使用 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() );