コレクション ウィジェットは、ギャラリー アプリの写真のコレクション、ニュースアプリの記事、通信アプリのメッセージなど、同じタイプの多くの要素の表示に特化しています。通常、コレクション ウィジェットは、コレクションの閲覧と、コレクションの要素を開いて詳細ビューを表示するという 2 つのユースケースに焦点を当てます。コレクション ウィジェットは上下にスクロールできます。
これらのウィジェットは、RemoteViewsService
を使用して、コンテンツ プロバイダなどのリモートデータに基づくコレクションを表示します。このウィジェットは、次のいずれかのビュータイプ(コレクション ビュー)を使用してデータを表示します。
ListView
- 上下にスクロールするリストにアイテムを表示するビュー。
GridView
- 2 次元のスクロール グリッドにアイテムを表示するビュー。
StackView
- 積み重ねられたカードビュー(Rolodex のようなもの)では、ユーザーがフロントカードを上または下にフリックすると、前または次のカードを表示できます。
AdapterViewFlipper
- 2 つ以上のビュー間でアニメーション化する、アダプターを使用するシンプルな
ViewAnimator
。一度に表示される子は 1 つだけです。
これらのコレクション ビューは、リモートデータに基づくコレクションを表示するため、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
サンプルから抜粋したものです。
このサンプルは、0 から 9 までの値を表示する 10 個のビューのスタックで構成されています。サンプル ウィジェットの主な動作は次のとおりです。
ユーザーは、ウィジェットのトップビューを垂直方向にフリングして、次のビューまたは前のビューを表示できます。これは組み込みの
StackView
動作です。ユーザーが操作しなくても、ウィジェットは自動的に、スライドショーのように順番にビューを進めます。これは、
res/xml/stackwidgetinfo.xml
ファイルのandroid:autoAdvanceViewId="@id/stack_view"
設定によるものです。この設定はビュー ID(この場合はスタックビューのビュー ID)に適用されます。ユーザーが上部のビューをタップすると、ウィジェットに「タッチビュー n」という
Toast
メッセージが表示されます。ここで、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
のサンプルで、ユーザーが上部のビューをタップすると、ウィジェットに「タッチビュー n」という Toast
メッセージが表示されます。ここで、n はタップされたビューのインデックス(位置)です。仕組みは次のとおりです。
StackWidgetProvider
(AppWidgetProvider
サブクラス)は、TOAST_ACTION
というカスタム アクションを持つペンディング インテントを作成します。ユーザーがビューをタップすると、インテントが起動し、
TOAST_ACTION
をブロードキャストします。このブロードキャストは、
StackWidgetProvider
クラスのonReceive()
メソッドによってインターセプトされ、タップされたビューについてToast
メッセージがウィジェットに表示されます。コレクション アイテムのデータは、RemoteViewsService
を通じてRemoteViewsFactory
によって提供されます。
ペンディング インテント テンプレートをセットアップする
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() );