集合小工具專門用於顯示同類型的多個元素,例如相片庫應用程式中的相片集合、新聞應用程式中的文章,或是通訊應用程式中的訊息。集合小工具通常著重於兩種用途:瀏覽集合,以及開啟集合元素的詳細檢視畫面。集合小工具可垂直捲動。
這些小工具會使用 RemoteViewsService
顯示遠端資料支援的集合,例如內容供應器。小工具會使用下列其中一種檢視類型 (稱為收集檢視畫面) 呈現資料:
ListView
- 顯示垂直捲動清單中項目的檢視畫面。
GridView
- 以二維捲動格線顯示項目的檢視畫面。
StackView
- 堆疊式資訊卡檢視畫面 (類似 Rolodex),使用者可以向上或向下滑動前方資訊卡,分別查看上一個或下一個資訊卡。
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
範例:
這個範例含有十個檢視畫面的堆疊,顯示值為零到九。範例小工具的主要行為如下:
使用者可以垂直揮動小工具中的頂端檢視畫面,顯示下一個或上一個檢視畫面。這是內建
StackView
的行為。在沒有任何使用者互動情況下,小工具會自動依序瀏覽其檢視畫面,就像幻燈片一樣。這是由於
res/xml/stackwidgetinfo.xml
檔案中的android:autoAdvanceViewId="@id/stack_view"
設定所致。這項設定適用於檢視畫面 ID,在本例中是堆疊檢視畫面的檢視畫面 ID。如果使用者觸碰頂端檢視畫面,小工具會顯示
Toast
訊息「Touched view n」,其中 n 是觸碰檢視畫面的索引 (位置)。如要進一步瞭解如何實作行為,請參閱「為個別項目新增行為」一節。
使用集合實作小工具
如要實作含有集合的 Widget,請按照實作任何 Widget 的程序操作,然後再執行幾個額外步驟:修改資訊清單、在 Widget 版面配置中新增集合檢視畫面,以及修改 AppWidgetProvider
子類別。
含有集合的資訊清單
除了在資訊清單中宣告小工具中列出的必要條件之外,您還需要讓含有集合的小工具能夠繫結至 RemoteViewsService
。方法是在資訊清單檔案中使用 BIND_REMOTEVIEWS
權限宣告服務。這會防止其他應用程式自由存取小工具的資料。
舉例來說,如果您建立的小工具使用 RemoteViewsService
填入集合檢視畫面,資訊清單項目可能會如下所示:
<service android:name="MyWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
在此範例中,android:name="MyWidgetService"
是指 RemoteViewsService
的子類別。
含有集合的 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>
請注意,空白檢視畫面必須是收集檢視畫面的同胞節點,空白檢視畫面代表空白狀態。
除了整個小工具的版面配置檔案外,請建立另一個版面配置檔案,定義集合中的每個項目的版面配置,例如書籍集合中的每本書籍的版面配置。StackWidget
範例只有一個商品版面配置檔案 (widget_item.xml
),因為所有商品都使用相同的版面配置。
適用於含有集合的小工具的 AppWidgetProvider 類別
與一般小工具一樣,AppWidgetProvider
子類別中的大部分程式碼通常會放在 onUpdate()
中。在建立含有集合的資訊方塊時,onUpdate()
的實作方式與一般情況的主要差異,在於您必須呼叫 setRemoteAdapter()
。這會告訴收集檢視畫面資料的來源。RemoteViewsService
隨後可傳回 RemoteViewsFactory
的實作項目,小工具就能提供適當的資料。呼叫此方法時,請傳遞意圖,指向您實作的 RemoteViewsService
,以及指定要更新的小工具的小工具 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()
,為集合中的個別項目新增點擊行為。這項操作包括為集合檢視畫面設定待處理意圖範本,然後透過 RemoteViewsFactory
為集合中的每個項目設定填入意圖。
本節使用 StackWidget
範例,說明如何在個別項目中新增行為。在 StackWidget
範例中,如果使用者輕觸頂端檢視畫面,小工具會顯示 Toast
訊息「Touched view n」,其中「n」是觸控檢視區塊的索引 (位置)。這類廣告運作方式如下:
StackWidgetProvider
(AppWidgetProvider
子類別) 會使用名為TOAST_ACTION
的自訂動作建立待處理意圖。使用者觸碰檢視畫面時,意圖會觸發並廣播
TOAST_ACTION
。這個廣播訊息會由
StackWidgetProvider
類別的onReceive()
方法攔截,小工具也會顯示觸控檢視畫面的Toast
訊息。RemoteViewsFactory
會透過RemoteViewsService
提供收藏項目的資料。
設定待處理意圖範本
StackWidgetProvider
(AppWidgetProvider
子類別) 會設定待處理意圖。集合中的個別項目無法設定自己的待處理意圖。相反地,整個集合會設定待處理意圖範本,而個別項目會設定填入意圖,以便針對個別項目建立獨特的行為。
這個類別也會接收使用者觸碰檢視畫面時傳送的廣播訊息。會在其 onReceive()
方法中處理此事件。如果意圖的動作為 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); } }
設定填入意圖
您的 RemoteViewsFactory
必須為集合中的每個項目設定填入意圖。如此一來,即可區分指定項目的個別點擊動作。接著,系統會將填入意圖與 PendingIntent
範本結合,以決定在輕觸項目時執行的最終意圖。
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
的互動方式,以及如何觸發更新:
使用集合的動態資訊方塊可為使用者提供最新內容。舉例來說,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() );