コレクション ウィジェットを使用する

コレクション ウィジェットは、ギャラリー アプリの写真のコレクション、ニュースアプリの記事、コミュニケーション アプリのメッセージなど、同じタイプの要素を多数表示することに特化しています。コレクション ウィジェットは通常、コレクションのブラウジングと、コレクションの要素を開いて詳細ビューを表示するという 2 つのユースケースに焦点を当てています。コレクション ウィジェットは上下にスクロールできます。

これらのウィジェットでは 表示する RemoteViewsService リモート データに基づくコレクション( できます。ウィジェットは、次のいずれかのビュータイプ(コレクション ビュー)を使用してデータを表示します。

ListView
特定のフォルダ内でアイテムを表示するビュー 縦方向にスクロールするリストです。
GridView
上下左右にスクロールできるグリッド方式でアイテムを表示するビュー。
StackView
縦に積まれたカード 前面をフリックすると、Rolodex のようなビューです。 前または次のカードを表示するために、カードを上または下にスクロールします。
AdapterViewFlipper
アダプターを使用する(シンプル) アニメーション化する ViewAnimator ビュー間の調整も可能です一度に 1 つの子だけが表示されます。

これらのコレクション ビューはリモートデータに基づくコレクションを表示するため、Adapter を使用してユーザー インターフェースをデータにバインドします。Adapter は、1 セットのデータから個々のアイテムを個別の View オブジェクトにバインドします。

こうしたコレクション ビューはアダプタを使用するため、Android フレームワークにはウィジェットでの使用をサポートするための追加のアーキテクチャが必要です。ウィジェットのコンテキストでは、AdapterRemoteViewsFactory に置き換えられます。これは、Adapter インターフェースのシンラッパーです。コレクション内の特定のアイテムについてリクエストされた場合、RemoteViewsFactory はコレクションのアイテムを作成して RemoteViews オブジェクトとして返します。ウィジェットにコレクション ビューを含めるには、RemoteViewsServiceRemoteViewsFactory を実装します。

RemoteViewsService は、リモート アダプタが RemoteViews オブジェクトをリクエストできるようにするサービスです。RemoteViewsFactory は、コレクション ビュー(ListViewGridViewStackView など)とそのビューの元となるデータの間のアダプタのためのインターフェースです。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 サンプル:

図 1. StackWidget

このサンプルは、値 0 を表示する 10 個のビューのスタックで構成されています。 表示されます。サンプル ウィジェットの主な動作は以下のとおりです。

  • ユーザーがウィジェットの最前面のビューを上下に動かして次または前のビューを表示できる。これは組み込みの StackView の動作です。

  • ユーザーが操作しなくても、ウィジェットは自動的に スライドショーのように順番に表示できます。これは、res/xml/stackwidgetinfo.xml ファイルでの android:autoAdvanceViewId="@id/stack_view" の設定によります。この設定はビュー ID に適用され、この場合はスタック ビューのビュー ID が対象となります。

  • ユーザーが最前面のビューにタッチすると、ウィジェットに Toast メッセージ「ビュー n にタッチしました」が表示されます(n はタッチされたビューのインデックス(位置)を示します)。Google Cloud のオペレーションスイートと 動作を実装するには、動作を個々に items セクションにあります。

コレクションを使用するウィジェットを実装する

コレクションを使用するウィジェットを実装するには、次の手順に沿って、 ウィジェットを実行してから、さらにいくつかの手順を実施します。 マニフェストの変更、ウィジェット レイアウトへのコレクション ビューの追加、 AppWidgetProvider サブクラス。

コレクションを使用するウィジェットのマニフェスト

マニフェストでウィジェットを宣言するで列挙した要件に加えて、コレクションを使用するウィジェットが RemoteViewsService にバインドできるようにする必要があります。これを行うには、マニフェスト ファイルで権限 BIND_REMOTEVIEWS を使用してサービスを宣言します。これにより、他のアプリはウィジェットのデータに自由にアクセスできなくなります。

たとえば、RemoteViewsService を使用して マニフェスト エントリは次のようになります。

<service android:name="MyWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

この例では、android:name="MyWidgetService"RemoteViewsService

コレクションを使用したウィジェットのレイアウト

ウィジェットのレイアウト XML ファイルの主な要件は、ListViewGridViewStackViewAdapterViewFlipper のコレクション ビューのいずれかを含むことです。これは、次の widget_layout.xml ファイルです。 StackWidget サンプル:

<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>

空のビューは、 empty view は、空の状態を表します。

ウィジェット全体のレイアウト ファイルに加えて、別のレイアウトを作成します。 コレクション内の各アイテムのレイアウトを定義するファイル(例: 書籍のコレクション内の各書籍のレイアウト。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

具体的な手順は次のとおりです。

  1. サブクラス RemoteViewsServiceRemoteViewsService は、リモート アダプタが RemoteViews をリクエストできるようにするサービスです。

  2. RemoteViewsService サブクラスに、 RemoteViewsFactory インターフェース。RemoteViewsFactory は、リモート コレクション ビュー(ListViewGridViewStackView など)とそのビューの元となるデータの間のアダプタのためのインターフェースです。データセット内の各アイテムについて RemoteViews オブジェクトを作成する作業は実装の責任です。このインターフェースは Adapter のシンラッパーです。

サービスの単一のインスタンス、またはそこに含まれるデータを使用して、 維持します。静的でない限り、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()。この作業では、VM のペンディング インテント テンプレートを コレクション ビューに追加してから、コレクション ビュー内の商品アイテムごとに RemoteViewsFactory のコレクション。

このセクションでは、StackWidget サンプルを使用して、動作を追加する方法について説明します。 表示されます。StackWidget のサンプルでは、ユーザーが上部ビューにタッチすると、 ウィジェットには「タッチビュー n」というメッセージ Toast が表示されます。ここで、n は タッチされたビューのインデックス(位置)。効果的な宣伝のアイデアをご紹介します。

  • StackWidgetProvider - AppWidgetProvider サブクラス - 次のカスタム アクションを持つペンディング インテントを作成します。 TOAST_ACTION

  • ユーザーがビューをタップすると、インテントが起動し、ブロードキャストして TOAST_ACTION

  • このブロードキャストは、StackWidgetProvider クラスの onReceive() メソッドにより、ウィジェットに Toast メッセージが表示されます。 クリックします。コレクション項目のデータは、 RemoteViewsService 経由で RemoteViewsFactory

ペンディング インテント テンプレートを設定する

StackWidgetProviderAppWidgetProvider サブクラス) ペンディング インテントを設定します。コレクション内の個々のアイテムは自分のペンディング インテントをセットアップできません。コレクション全体がペンディング インテント テンプレートをセットアップし、個々のアイテムがフィルイン インテントを設定して、アイテムごとに一意の動作を作成します。

このクラスは、ユーザーがタップしたときに送信されるブロードキャストも受け取ります。 表示されます。このイベントを 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 とやり取りする方法と、 更新をトリガーします。

図 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()
);