コレクション ウィジェットは、ギャラリー アプリの写真のコレクション、ニュースアプリの記事、コミュニケーション アプリのメッセージなど、同じタイプの要素を多数表示することに特化しています。コレクション ウィジェットは通常、コレクションのブラウジングと、コレクションの要素を開いて詳細ビューを表示するという 2 つのユースケースに重点を置いています。コレクション ウィジェットは上下にスクロールできます。
これらのウィジェットは、RemoteViewsService
を使用して、コンテンツ プロバイダなどからのリモート データを利用したコレクションを表示します。ウィジェットは、次のいずれかのビュータイプ(コレクション ビュー)を使用してデータを表示します。
ListView
- 上下にスクロールできるリスト形式でアイテムを表示するビュー。
GridView
- 上下左右にスクロールできるグリッド方式でアイテムを表示するビュー。
StackView
- 重なったカードが(回転式カードファイルのように)表示され、最前面のカードを上にフリックすると前のカード、下にフリックすると次のカードが表示されるビューです。
AdapterViewFlipper
- アダプタを使用するシンプルな
ViewAnimator
で、複数のビューをアニメーションで切り替えます。一度に 1 つの子だけが表示されます。
これらのコレクション ビューはリモートデータに基づくコレクションを表示するため、Adapter
を使用してユーザー インターフェースをデータにバインドします。Adapter
は、1 セットのデータから個々のアイテムを個別の 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
サンプルから抜粋したものです。
このサンプルは、値 0 ~ 9 を表示する 10 件のビューのスタックで構成されています。サンプル ウィジェットの主な動作は以下のとおりです。
ユーザーがウィジェットの最前面のビューを上下に動かして次または前のビューを表示できる。これは
StackView
に組み込まれた動作です。ユーザーの操作なしに、ウィジェットが自動的に(スライドショーのように)順番にビューを表示する。これは、
res/xml/stackwidgetinfo.xml
ファイルでのandroid:autoAdvanceViewId="@id/stack_view"
の設定によります。この設定はビュー ID に適用され、この場合はスタック ビューのビュー ID が対象となります。ユーザーが最前面のビューにタッチすると、ウィジェットに
Toast
メッセージ「ビュー 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
1 つだけです。
コレクションを使用するウィジェットの 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
のシンラッパーです。
サービスの 1 つのインスタンス、またはそれに含まれるデータは維持されません。静的でない限り、RemoteViewsService
にデータを保存しないでください。ウィジェットのデータを保持する場合は、データがプロセスのライフサイクルを超えて保持される ContentProvider
を使用するのが最善の方法です。たとえば、食料品店のウィジェットは、各買い物リストアイテムの状態を SQL データベースなどの永続的な場所に保存できます。
RemoteViewsService
実装のメイン コンテンツは RemoteViewsFactory
です。これについて下で説明します。
RemoteViewsFactory インターフェース
RemoteViewsFactory
インターフェースを実装するカスタムクラスは、コレクション内のアイテムのデータをウィジェットに提供します。このために、ウィジェット アイテムの XML レイアウト ファイルにデータのソースを組み合わせます。このデータソースは、データベースでも単純な配列でも構いません。StackWidget
サンプルでは、データソースは WidgetItems
の配列です。RemoteViewsFactory
は、データをリモート コレクション ビューに結びつけるアダプタとして機能します。
RemoteViewsFactory
サブクラスに実装する必要がある特に重要な 2 つのメソッドは、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()
は、データセット内の指定された position
にあるデータに対応する RemoteViews
オブジェクトを返します。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
メッセージ「ビュー 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()
を呼び出す必要もありません。
このアプローチでは、アダプタへのデータの入力が容易になるだけでなく、ユーザーがリストを下にスクロールして新しいアイテムを表示する際の新しいアイテムの入力レイテンシも解消されます。コレクション アイテムのセットが比較的小さい場合は、このアダプターの設定方法が推奨されます。ただし、たとえば、コレクションに 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() );