アプリ ウィジェットを作成する

アプリ ウィジェットとは、他のアプリ(ホーム画面など)に埋め込んで定期的に更新を取得することができる小さなアプリビューです。このようなビューをユーザー インターフェースではウィジェットと呼び、ウィジェットはアプリ ウィジェット プロバイダを使用して公開できます。他のアプリ ウィジェットを格納できるアプリ コンポーネントをアプリ ウィジェット ホストと呼びます。下のスクリーンショットは音楽アプリ ウィジェットです。

このドキュメントでは、アプリ ウィジェット プロバイダを使用してアプリ ウィジェットを公開する方法について説明します。アプリ ウィジェットをホストする AppWidgetHost を作成する方法については、アプリ ウィジェット ホストをご覧ください。

注: アプリ ウィジェットの設計方法については、アプリ ウィジェットの概要をご覧ください。

基本情報

アプリ ウィジェットを作成するには、以下のものが必要です。

AppWidgetProviderInfo オブジェクト
アプリ ウィジェットのメタデータ(アプリ ウィジェットのレイアウト、更新頻度、AppWidgetProvider クラスなど)を記述します。これは XML で定義する必要があります。
AppWidgetProvider クラスの実装
ブロードキャスト イベントに基づいて、アプリ ウィジェットとのインターフェースをプログラムで実現する基本的なメソッドを定義します。アプリ ウィジェットが更新、有効化、無効化、削除されたときにこのクラスを介してブロードキャストが届きます。
ビューのレイアウト
アプリ ウィジェットの初期レイアウトを XML で定義します。

また、アプリ ウィジェット設定アクティビティを実装することもできます。ユーザーがアプリ ウィジェットを追加したときに起動し、アプリ ウィジェットの設定を作成時に変更できるようにする Activity です(省略可)。

以降のセクションで、各コンポーネントのセットアップ方法について説明します。

マニフェストでアプリ ウィジェットを宣言する

まず、アプリの AndroidManifest.xml ファイルで AppWidgetProviderクラスを宣言します。例:

    <receiver android:name="ExampleAppWidgetProvider" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/example_appwidget_info" />
    </receiver>
    

<receiver> 要素は android:name 属性を必要とします。アプリ ウィジェットで使用される AppWidgetProvider を指定する属性です。

<intent-filter> 要素には、<action> 要素を android:name 属性とともに含める必要があります。この属性は、AppWidgetProviderACTION_APPWIDGET_UPDATE ブロードキャストを受け入れることを指定します。明示的に宣言する必要があるブロードキャストはこれだけです。AppWidgetManager は、必要に応じて他のすべてのアプリ ウィジェット ブロードキャストを自動的に AppWidgetProvider に送信します。

<meta-data> 要素は、AppWidgetProviderInfo リソースを指定し、以下の属性を必要とします。

  • android:name - メタデータ名を指定します。android.appwidget.provider を使用してデータを AppWidgetProviderInfo 記述子として特定してください。
  • android:resource - AppWidgetProviderInfo リソースの場所を指定します。

AppWidgetProviderInfo メタデータを追加する

AppWidgetProviderInfo は、最小レイアウト サイズ、初期レイアウト リソース、アプリ ウィジェットの更新頻度、作成時に起動する設定アクティビティ(省略可)など、アプリ ウィジェットの基本的な品質を定義します。単一の <appwidget-provider> 要素を使用して XML リソースで AppWidgetProviderInfo オブジェクトを定義し、プロジェクトの res/xml/ フォルダに保存します。

例:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="40dp"
        android:minHeight="40dp"
        android:updatePeriodMillis="86400000"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/example_appwidget"
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="home_screen">
    </appwidget-provider>
    

<appwidget-provider> 属性をまとめると以下のとおりとなります。

  • minWidth および minHeight 属性の値は、アプリ ウィジェットがデフォルトで消費する最小の容量を指定します。デフォルトのホーム画面では、アプリ ウィジェットは高さと幅が定義されたセルのグリッドに基づいてウィンドウ内に配置されます。アプリ ウィジェットの最小の幅または高さの値がセルのサイズと一致しない場合、アプリ ウィジェットのサイズは最も近いセルサイズに切り上げられます。

    アプリ ウィジェットのサイズ設定について詳しくは、アプリ ウィジェットの設計ガイドラインをご覧ください。

    注: アプリ ウィジェットをデバイス間で移植可能にするには、アプリ ウィジェットの最小サイズを 4 x 4 セル以下にします。

  • minResizeWidth および minResizeHeight 属性は、アプリ ウィジェットの絶対最小サイズを指定します。この値は、アプリ ウィジェットが判読不能または使用不能にならない範囲の最小サイズとする必要があります。こうした属性を使用すると、ユーザーがウィジェットのサイズを minWidth および minHeight 属性で定義されたデフォルトのウィジェット サイズよりも小さいサイズにも変更できるようになります。Android 3.1 で導入されました。

    アプリ ウィジェットのサイズ設定について詳しくは、アプリ ウィジェットの設計ガイドラインをご覧ください。

  • updatePeriodMillis 属性は、アプリ ウィジェット フレームワークが onUpdate() コールバック メソッドを呼び出して AppWidgetProvider に更新をリクエストする頻度を定義します。実際の更新がこの値で正確に行われることを保証するものではありません。電池を節約するため、できるだけ低い頻度(たとえば 1 時間に 1 回以下)に設定することをおすすめします。ユーザーが設定で頻度を調整できるようにすることもできます。たとえば、株価情報を 15 分おきに更新する、1 日 4 回のみ更新する、といった設定ができます。

    注:updatePeriodMillis で定義された)更新のタイミングでデバイスがスリープになっていた場合、更新を実行するためにスリープが解除されます。ただ、1 時間に 1 回以下の更新頻度であれば、電池の持続時間に深刻な影響が生じることはないでしょう。更新頻度をそれより高くする場合やデバイスのスリープ時に更新しないようにする場合は、アラームに基づいて更新を実行するようにすればスリープは解除されません。その場合は、AlarmManager を使用して、AppWidgetProvider が受信するインテントでアラームを設定します。アラームのタイプを ELAPSED_REALTIME または RTC に指定します。するとデバイスがスリープでないときにのみアラームが配信されます。それから、updatePeriodMillis をゼロ("0")に設定します。

  • initialLayout 属性は、アプリ ウィジェット レイアウトを定義するレイアウト リソースを指定します。
  • configure 属性は、ユーザーがアプリ ウィジェットを追加したときに起動してアプリ ウィジェット プロパティを設定できるようにする Activity を定義します。この属性は省略可です(下記のアプリ ウィジェットの設定アクティビティを作成するをご覧ください)。
  • previewImage 属性は、アプリ ウィジェットの設定後にユーザーがアプリ ウィジェットを選択したときに表示されるアプリ ウィジェットのプレビューを指定します。この属性が指定されていない場合はアプリのランチャー アイコンが表示されます。このフィールドは、AndroidManifest.xml ファイル内の <receiver> 要素の android:previewImage 属性に対応します。previewImage の使用方法について詳しくは、プレビュー画像を設定するをご覧ください。Android 3.0 で導入されました。
  • autoAdvanceViewId 属性は、ウィジェットのホストによって自動進行されるアプリ ウィジェット サブビューのビュー ID を指定します。Android 3.0 で導入されました。
  • resizeMode 属性は、ウィジェットのサイズ変更に関するルールを指定します。ホーム画面ウィジェットの縦、横、またはその両方のサイズを変更できるようにする場合にこの属性を使用します。ユーザーがウィジェットを長押しするとサイズ変更ハンドルが表示され、縦または横のハンドルをドラッグしてレイアウト グリッド上でサイズを変更できるようになります。resizeMode 属性の値には、「horizontal」、「vertical」、「none」があります。縦と横の両方のサイズを変更できるウィジェットを宣言するには、値を「horizontal|vertical」に指定します。Android 3.1 で導入されました。
  • minResizeHeight 属性は、ウィジェットのサイズ変更で指定できる高さの最小値を(dp で)指定します。値が minHeight を超えている場合、または縦のサイズ変更が有効になっていない場合(resizeMode を参照)、このフィールドは無効です。Android 4.0 で導入されました。
  • minResizeWidth 属性は、ウィジェットのサイズ変更で指定できる幅の最小値を(dp で)指定します。値が minWidth を超えている場合、または横のサイズ変更が有効になっていない場合(resizeMode を参照)、このフィールドは無効です。Android 4.0 で導入されました。
  • widgetCategory 属性は、アプリ ウィジェットをホーム画面(home_screen)、ロック画面(keyguard)、またはその両方に表示できるかどうかを宣言します。ロック画面ウィジェットをサポートしているのは Android 5.0 未満のバージョンのみです。Android 5.0 以上では home_screen のみ有効です。

<appwidget-provider> 要素で指定できる属性について詳しくは、AppWidgetProviderInfo クラスをご覧ください。

アプリ ウィジェット レイアウトを作成する

アプリ ウィジェットの初期レイアウトを XML で定義し、プロジェクトの res/layout/ ディレクトリに保存する必要があります。アプリ ウィジェットの設計は、下記のビュー オブジェクトを使用して行うことができますが、アプリ ウィジェットの設計を始める前に、アプリ ウィジェットの設計ガイドラインをよくお読みください。

レイアウトに慣れているデベロッパーであれば、アプリ ウィジェットのレイアウトの作成は簡単に行えます。ただし、アプリ ウィジェットのレイアウトは RemoteViews をベースとしており、すべての種類のレイアウトやビューがサポートされているわけではないことに注意が必要です。

RemoteViews オブジェクトは(結果としてアプリ ウィジェットも)以下のレイアウト クラスをサポートできます。

また、以下のウィジェット クラスをサポートできます。

上記のクラスの子孫はサポートされません。

RemoteViews は ViewStub もサポートします。これはサイズがゼロの見えないビューで、これを使用してランタイムにレイアウト リソースを遅延インフレートすることができます。

アプリ ウィジェットに余白を追加する

ウィジェットは通常、上下左右が画面の端に届かないようにし、他のウィジェットとも直接接しないようにする必要があるため、ウィジェット フレームの上下左右に余白を追加します。

Android 4.0 では、アプリ ウィジェットのフレームと境界ボックスの間に自動的にパディングが追加され、ユーザーのホーム画面で他のウィジェットやアイコンとの位置関係がより適切に調整されるようになっています。この機能を活用できるよう、アプリの targetSdkVersion を 14 以上に設定してください。

以下の手順で、Android 4.0 未満のバージョン向けにはカスタムの余白を適用し、Android 4.0 以上向けには余白を追加しないとする 1 つのレイアウトを簡単に作成することができます。

  1. アプリの targetSdkVersion を 14 以上に設定します。
  2. 余白としてサイズリソースを参照する以下のようなレイアウトを作成します。
        <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:padding="@dimen/widget_margin">
    
          <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:background="@drawable/my_widget_background">
            …
          </LinearLayout>
    
        </FrameLayout>
        
  3. res/values/ で Android 4.0 未満のバージョン向けにカスタムの余白を設定するサイズリソースと、res/values-v14/ で Android 4.0 のウィジェット向けにパディングを追加しないとするサイズリソースを作成します。

    res/values/dimens.xml:

    <dimen name="widget_margin">8dp</dimen>

    res/values-v14/dimens.xml:

    <dimen name="widget_margin">0dp</dimen>

上記のほかに、デフォルトでは NinePatch 背景アセットに追加の余白を組み込み、API レベル 14 以上については余白のない別の NinePatch を用意する方法もあります。

AppWidgetProvider クラスを使用する

AppWidgetProvider クラスは、アプリ ウィジェットのブロードキャストを処理するコンビニエンス クラスとして BroadcastReceiver を拡張します。AppWidgetProvider は、アプリ ウィジェットの更新、削除、有効化、無効化など、アプリ ウィジェットに関係するイベント ブロードキャストのみを受け取ります。こうしたブロードキャスト イベントが発生したときに、AppWidgetProvider は以下のメソッドの呼び出しを受け取ります。

onUpdate()
AppWidgetProviderInfo の updatePeriodMillis 属性で定義された間隔でアプリ ウィジェットを更新するために呼び出されます(上記の AppWidgetProviderInfo メタデータを追加するを参照)。このメソッドは、ユーザーがアプリ ウィジェットを追加したときにも呼び出され、必要に応じてビューのためのイベント ハンドラを定義する、一時的な Service を開始するなど、基本的なセットアップを実行します。ただし、設定アクティビティを宣言している場合は、このメソッドはユーザーがアプリ ウィジェットを追加したときには呼び出されず、以後の更新時に呼び出されます。設定の完了時に最初の更新を実行するのは設定アクティビティの責任です(下記のアプリ ウィジェットの設定アクティビティを作成するを参照)。
onAppWidgetOptionsChanged()
ウィジェットが最初に配置されたとき、および以後ウィジェットのサイズが変更されたときに呼び出されます。このコールバックを使用して、ウィジェットのサイズ範囲に基づいてコンテンツを表示または非表示にできます。サイズ範囲は getAppWidgetOptions() を呼び出して取得できます。以下の情報を含む Bundle が返されます。

このコールバックは API レベル 16(Android 4.1)で導入されました。このコールバックを実装する場合、アプリがこのコールバックに依存しないようにしてください(古いデバイスでは呼び出されないため)。
onDeleted(Context, int[])
このメソッドは、アプリ ウィジェットがアプリ ウィジェット ホストから削除されるたびに呼び出されます。
onEnabled(Context)
このメソッドは、アプリ ウィジェットのインスタンスが初めて作成されたときに呼び出されます。たとえば、ユーザーがアプリ ウィジェットのインスタンスを 2 つ追加した場合は、初回のみ呼び出されます。新しいデータベースを開く必要がある場合や、その他アプリ ウィジェットのすべてのインスタンスについて計 1 回だけ必要なセットアップを行う必要がある場合は、このタイミングが適しています。
onDisabled(Context)
このメソッドは、アプリ ウィジェットの最後のインスタンスがアプリ ウィジェット ホストから削除されたときに呼び出されます。onEnabled(Context) で行った作業がある場合はこのタイミングで消去します(一時データベースを削除するなど)。
onReceive(Context, Intent)
このメソッドは、ブロードキャストごとに、また上記の各コールバック メソッドの前に呼び出されます。デフォルトの AppWidgetProvider 実装では、アプリ ウィジェットのすべてのブロードキャストをフィルタリングし、必要に応じて上記のメソッドを呼び出すため、通常はこのメソッドを実装する必要はありません。

ManifestAndroidManifest の <receiver> 要素を使用して、AppWidgetProvider クラスの実装をブロードキャスト レシーバとして宣言する必要があります(上記のマニフェストでアプリ ウィジェットを宣言するを参照)。

最も重要な AppWidgetProvider コールバックは onUpdate() です。各アプリ ウィジェットがホストに追加されたときに呼び出されるためです(設定アクティビティを使用する場合を除く)。アプリ ウィジェットがユーザー操作イベントを受け入れる場合、このコールバックにイベント ハンドラを登録する必要があります。アプリ ウィジェットが一時ファイルやデータベースを作成したり、クリーンアップが必要な他の作業を行ったりすることがない場合は、onUpdate() のみを定義すれば足りる場合があります。たとえば、クリックするとアクティビティを起動するボタンのあるアプリ ウィジェットを作成する場合、AppWidgetProvider の以下のような実装を使用することが考えられます。

Kotlin

    class ExampleAppWidgetProvider : AppWidgetProvider() {

        override fun onUpdate(
                context: Context,
                appWidgetManager: AppWidgetManager,
                appWidgetIds: IntArray
        ) {
            // Perform this loop procedure for each App Widget that belongs to this provider
            appWidgetIds.forEach { appWidgetId ->
                // Create an Intent to launch ExampleActivity
                val pendingIntent: PendingIntent = Intent(context, ExampleActivity::class.java)
                        .let { intent ->
                            PendingIntent.getActivity(context, 0, intent, 0)
                        }

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                val views: RemoteViews = RemoteViews(
                        context.packageName,
                        R.layout.appwidget_provider_layout
                ).apply {
                    setOnClickPendingIntent(R.id.button, pendingIntent)
                }

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views)
            }
        }
    }
    

Java

    public class ExampleAppWidgetProvider extends AppWidgetProvider {

        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            final int N = appWidgetIds.length;

            // Perform this loop procedure for each App Widget that belongs to this provider
            for (int i=0; i<N; i++) {
                int appWidgetId = appWidgetIds[i];

                // Create an Intent to launch ExampleActivity
                Intent intent = new Intent(context, ExampleActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
                views.setOnClickPendingIntent(R.id.button, pendingIntent);

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    }
    

この AppWidgetProvider は、Activity を起動する PendingIntent を定義してこれを setOnClickPendingIntent(int, PendingIntent) でアプリ ウィジェットのボタンにアタッチする目的で、onUpdate() メソッドのみを定義します。appWidgetIds の各エントリで繰り返されるループがあることに注意してください。このプロバイダが作成した各アプリ ウィジェットを特定する ID の配列です。このようにすると、ユーザーがアプリ ウィジェットの複数のインスタンスを作成した場合に、それらがすべて同時に更新されるようになります。ただし、アプリ ウィジェットのすべてのインスタンスについて 1 つの updatePeriodMillis スケジュールのみが管理されます。たとえば、更新スケジュールが 2 時間おきと定義されていて、アプリ ウィジェットの 2 番目のインスタンスが最初のアプリ ウィジェットの 1 時間後に追加された場合、2 番目のアプリ ウィジェットの更新頻度は無視され、いずれのインスタンスも最初のアプリ ウィジェットで定義された頻度で更新されます(いずれも 1 時間ごとではなく 2 時間ごとに更新されます)。

注: AppWidgetProviderBroadcastReceiver を拡張するため、コールバック メソッドの応答が返された後もプロセスの実行が継続するとは限りません(ブロードキャストのライフサイクルについては BroadcastReceiver を参照)。アプリ ウィジェットのセットアップ プロセスが完了するまでに数秒かかる(おそらくウェブ リクエストが実行されているため)ことがあり、プロセスが継続される必要がある場合は、onUpdate() メソッドで Service を開始することを検討します。サービスの中から、アプリケーション応答なし(ANR)のエラーによって AppWidgetProvider が終了してしまうことなく、アプリ ウィジェットの独自の更新を実行できます。Service を実行するアプリ ウィジェットの例としては、Wiktionary のサンプルの AppWidgetProvider をご覧ください。

ExampleAppWidgetProvider.java サンプルクラスもご覧ください。

アプリ ウィジェットのブロードキャスト インテントを受け取る

AppWidgetProvider はあくまでコンビニエンス クラスです。アプリ ウィジェットのブロードキャストを直接受け取るようにするには、独自の BroadcastReceiver を実装するか、onReceive(Context, Intent) コールバックをオーバーライドする方法があります。注意が必要なインテントは以下のとおりです。

アプリ ウィジェットを固定する

Android 8.0(API レベル 26)以上を実行しているデバイスでは、固定ショートカットを作成できるランチャーで、アプリ ウィジェットをランチャーに固定することもできます。固定ショートカットと同様に、そのようにして固定したウィジェットから、ユーザーはアプリ内の特定のタスクにアクセスできます。

以下の一連の手順を実行することで、アプリ内で、サポートされているランチャーにウィジェットを固定するリクエストを作成できます。

  1. 以下のスニペットのように、アプリのマニフェスト ファイル内にウィジェットを作成します。
        <manifest>
        ...
          <application>
            ...
            <receiver android:name="MyAppWidgetProvider">
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                           android:resource="@xml/my_appwidget_info" />
            </receiver>
          </application>
        </manifest>
        
  2. 以下のコード スニペットのように requestPinAppWidget() メソッドを呼び出します。

    Kotlin

        val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
        val myProvider = ComponentName(context, MyAppWidgetProvider::class.java)
    
        val successCallback: PendingIntent? = if (appWidgetManager.isRequestPinAppWidgetSupported) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent(...).let { intent ->
                // Configure the intent so that your app's broadcast receiver gets
                // the callback successfully. This callback receives the ID of the
                // newly-pinned widget (EXTRA_APPWIDGET_ID).
                PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            }
        } else {
            null
        }
    
        successCallback?.also { pendingIntent ->
            appWidgetManager.requestPinAppWidget(myProvider, null, pendingIntent)
        }
        

    Java

        AppWidgetManager appWidgetManager =
                context.getSystemService(AppWidgetManager.class);
        ComponentName myProvider =
                new ComponentName(context, MyAppWidgetProvider.class);
    
        if (appWidgetManager.isRequestPinAppWidgetSupported()) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent pinnedWidgetCallbackIntent = new Intent( ... );
    
            // Configure the intent so that your app's broadcast receiver gets
            // the callback successfully. This callback receives the ID of the
            // newly-pinned widget (EXTRA_APPWIDGET_ID).
            PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
                    pinnedWidgetCallbackIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
        }
        

注: ウィジェットがサポートされているランチャーに確かに固定されたかどうかをアプリに通知する必要がない場合は、null を第 3 の引数として requestPinAppWidget() に渡すこともできます。

アプリ ウィジェットの設定アクティビティを作成する

ユーザーが新しいアプリ ウィジェットを追加したときにユーザーが設定を行うようにするには、アプリ ウィジェットの設定アクティビティを作成する方法があります。この Activity は、アプリ ウィジェット ホストによって自動的に起動されます。これによってユーザーは作成時にアプリ ウィジェットの色、サイズ、更新期間などの機能の設定を行うことができます。

設定アクティビティは、Android マニフェストファイルの通常のアクティビティとして宣言します。ただし、アプリ ウィジェット ホストによって ACTION_APPWIDGET_CONFIGURE アクションで起動されるため、このアクティビティがこのインテントを受け入れるようにする必要があります。例:

    <activity android:name=".ExampleAppWidgetConfigure">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
        </intent-filter>
    </activity>
    

また、アクティビティは AppWidgetProviderInfo XML ファイルで android:configure 属性を使用して宣言する必要があります(上記の AppWidgetProviderInfo メタデータを追加するを参照)。たとえば、設定アクティビティは以下のように宣言できます。

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        ... >
    </appwidget-provider>
    

ここでアクティビティは、完全修飾名前空間を使用して宣言されています。パッケージ範囲の外から参照されるためです。

設定アクティビティの使用を開始するにあたって必要な作業は以上です。あとは実際のアクティビティを用意するだけです。ただし、アクティビティの実装にあたっては以下の 2 つの点に注意してください。

  • アプリ ウィジェット ホストが設定アクティビティを呼び出し、設定アクティビティが常に結果を返すようにします。結果には、アクティビティを起動したインテントから渡されたアプリ ウィジェット ID(インテントのエクストラに EXTRA_APPWIDGET_ID として保存される)が含まれている必要があります。
  • onUpdate() メソッドは、アプリ ウィジェットが作成されたときには呼び出されません(設定アクティビティが起動されたときにシステムから ACTION_APPWIDGET_UPDATE ブロードキャストは送信されません)。アプリ ウィジェットが最初に作成されたときに AppWidgetManager に更新をリクエストすることは設定アクティビティの責任です。ただし、onUpdate() は以後の更新のときには呼び出されます。スキップされるのは初回だけです。

設定から結果を返してアプリ ウィジェットを更新する方法の例として、次のセクションのコード スニペットをご覧ください。

設定アクティビティからアプリ ウィジェットを更新する

アプリ ウィジェットで設定アクティビティを使用する場合、設定完了時にアプリ ウィジェットを更新することはアクティビティの責任です。これを行うには、AppWidgetManager から直接更新をリクエストします。

アプリ ウィジェットを適切に更新して設定アクティビティを閉じる手順の概要は以下のとおりです。

  1. まず、アクティビティを起動したインテントからアプリ ウィジェット ID を取得します。

    Kotlin

        appWidgetId = intent?.extras?.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
        ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
        

    Java

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            appWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
        
  2. アプリ ウィジェットの設定を行います。
  3. 設定が完了したら、以下のように getInstance(Context) を呼び出して AppWidgetManager のインスタンスを取得します。

    Kotlin

        val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
        

    Java

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        
  4. 以下のように updateAppWidget(int, RemoteViews) を呼び出して、RemoteViews レイアウトでアプリ ウィジェットを更新します。

    Kotlin

        RemoteViews(context.packageName, R.layout.example_appwidget).also { views->
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
        

    Java

        RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.example_appwidget);
        appWidgetManager.updateAppWidget(appWidgetId, views);
        
  5. 最後に、返すインテントを作成し、アクティビティの結果で設定してから、アクティビティを終了します。

    Kotlin

        val resultValue = Intent().apply {
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        }
        setResult(Activity.RESULT_OK, resultValue)
        finish()
        

    Java

        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
        

ヒント: 設定アクティビティが最初に開いたとき、上記のステップ 5 のように、アクティビティの結果を EXTRA_APPWIDGET_ID とともに RESULT_CANCELED に設定します。そのようにすると、ユーザーが最後まで完了する前にアクティビティを停止した場合、設定がキャンセルされたことがアプリ ウィジェット ホストに通知され、アプリ ウィジェットが追加されないようになります。

例としては、ApiDemos の ExampleAppWidgetConfigure.java サンプルクラスをご覧ください。

プレビュー画像を設定する

Android 3.0 で、アプリ ウィジェットのプレビューを指定する previewImage フィールドが導入されました。このプレビューは、ウィジェット選択ツールを通じてユーザーに表示されます。このフィールドが指定されていない場合、アプリ ウィジェットのアイコンがプレビューに使用されます。

XML でこの設定を指定する方法は以下のとおりです。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      ...
      android:previewImage="@drawable/preview">
    </appwidget-provider>

Android Emulator には、アプリ ウィジェットのプレビュー画像を作成する(previewImage フィールドで指定する)際に利用できる「Widget Preview」というアプリケーションが用意されています。プレビュー画像を作成するには、このアプリケーションを起動し、アプリのアプリ ウィジェットを選択し、プレビュー画像の表示方法をセットアップしたうえで、保存してアプリのドローアブル リソース内に格納します。

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

Android 3.0 で、コレクションを使用するアプリ ウィジェットが導入されました。この種のアプリ ウィジェットは、RemoteViewsService を使用してコレクションを表示します。コレクションは、コンテンツ プロバイダなどからのリモート データを利用します。RemoteViewsService から提供されるデータは、以下のいずれかのビュータイプ(「コレクション ビュー」といいます)を使用してアプリ ウィジェットに表示されます。

ListView
上下にスクロールできるリスト形式でアイテムを表示するビュー。例としては Gmail アプリ ウィジェットがあります。
GridView
上下左右にスクロールできるグリッド方式でアイテムを表示するビュー。例としては、ブックマーク アプリ ウィジェットがあります。
StackView
重なったカードが(回転式カードファイルのように)表示され、最前面のカードを上にフリックすると前のカード、下にフリックすると次のカードが表示されるビューです。例としては、YouTube アプリ ウィジェットやブックス アプリ ウィジェットがあります。
AdapterViewFlipper
アダプタを使用するシンプルな ViewAnimator で、複数のビューをアニメーションで切り替えます。一度に 1 つの子だけが表示されます。

前述のように、こうしたコレクション ビューは、リモートデータに基づくコレクションを表示します。つまり、Adapter を使用してユーザー インターフェースをデータにバインドします。Adapter は、1 セットのデータから個々のアイテムを個別の View オブジェクトにバインドします。こうしたコレクション ビューはアダプタを使用するため、Android フレームワークにはアプリ ウィジェットでの使用をサポートするための追加のアーキテクチャが必要です。アプリ ウィジェットでは、AdapterRemoteViewsFactory に置き換えられます。これは、Adapter インターフェースのシンラッパーです。コレクション内の特定のアイテムについてリクエストされた場合、RemoteViewsFactory はコレクションのアイテムを作成して RemoteViews オブジェクトとして返します。アプリ ウィジェットにコレクション ビューを含めるには、RemoteViewsServiceRemoteViewsFactory を実装する必要があります。

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

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

Java

    public class StackWidgetService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

サンプルアプリ

このセクションのコードは、StackWidget サンプルから抜粋したものです。

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

  • ユーザーがアプリ ウィジェットの最前面のビューを上下に動かして次または前のビューを表示できる。これは StackView に組み込まれた動作です。
  • ユーザーの操作なしに、アプリ ウィジェットが自動的に(スライドショーのように)順番にビューを表示する。これは、res/xml/stackwidgetinfo.xml ファイルでの android:autoAdvanceViewId="@id/stack_view" の設定によります。この設定はビュー ID に適用され、この場合はスタック ビューのビュー ID が対象となります。
  • ユーザーが最前面のビューにタッチしたときに、アプリ ウィジェットが Toast メッセージ「n にタッチしました」を表示する(n はタッチされたビューのインデックス(位置)を表す)。実装方法について詳しくは、個々のアイテムに動作を追加するをご覧ください。

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

コレクションを使用するアプリ ウィジェットを実装するには、まず通常のアプリ ウィジェットの基本的な実装手順を行います。以降のセクションで、コレクションを使用するアプリ ウィジェットを実装する際に必要となる追加の手順について説明します。

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

マニフェストでアプリ ウィジェットを宣言するで列挙した要件に加えて、コレクションを使用するアプリ ウィジェットが RemoteViewsService にバインドできるようにするには、マニフェスト ファイルで BIND_REMOTEVIEWS を使用してサービスを宣言する必要があります。こうすることで、他のアプリがアプリ ウィジェットのデータに自由にアクセスできないようにします。たとえば、RemoteViewsService を使用してコレクション ビューにデータを入力するアプリ ウィジェットを作成する場合のマニフェスト エントリの例を以下に示します。

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

android:name="MyWidgetService" の行は RemoteViewsService のサブクラスを示しています。

コレクションを使用するアプリ ウィジェットのレイアウト

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

<?xml version="1.0" encoding="utf-8"?>

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <StackView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            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() を呼び出す必要があることです。このメソッドは、コレクション ビューにデータを取得する場所を指示します。すると RemoteViewsServiceRemoteViewsFactory の実装を返すことができ、ウィジェットは適切なデータを提供できます。このメソッドを呼び出すときは、RemoteViewsService の実装を指すインテントと、更新するアプリ ウィジェットを指定するアプリ ウィジェット ID を渡す必要があります。

例として、StackWidget サンプルから、onUpdate() コールバック メソッドを実装して RemoteViewsService をアプリ ウィジェット コレクションのリモート アダプタとして設定するコードを以下に示します。

Kotlin

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // update each of the app widgets with the remote adapter
        appWidgetIds.forEach { appWidgetId ->

            // Set up the intent that starts the StackViewService, which will
            // provide the views for this collection.
            val intent = Intent(context, StackWidgetService::class.java).apply {
                // Add the app 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 app widget layout.
            val rv = 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 should be in the same layout used to instantiate the RemoteViews
                // object above.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            //
            // Do additional processing specific to this app widget...
            //

            appWidgetManager.updateAppWidget(appWidgetId, rv)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }
    

Java

    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Set up the intent that starts the StackViewService, which will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            // Add the app 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 app widget layout.
            RemoteViews rv = 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.
            rv.setRemoteAdapter(R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items.
            // It should be in the same layout used to instantiate the RemoteViews
            // object above.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            //
            // Do additional processing specific to this app widget...
            //

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
    

RemoteViewsService クラス

データの維持

前述のとおり、RemoteViewsService サブクラスは、リモート コレクション ビューのデータ入力に使用される RemoteViewsFactory を提供します。

具体的には、以下の手順を実施する必要があります。

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

サービスの 1 つのインスタンス、またはそれに含まれるデータは維持されません。よって、(静的でない限り)データを RemoteViewsService に保存することは避ける必要があります。アプリ ウィジェットのデータを維持する方法としては、データがプロセスのライフサイクルを超えて維持される ContentProvider を使用するのが最善です。

RemoteViewsService 実装のメイン コンテンツは RemoteViewsFactory です。これについて下で説明します。

RemoteViewsFactory インターフェース

RemoteViewsFactory インターフェースを実装するカスタムクラスは、コレクション内のアイテムのデータをアプリ ウィジェットに提供します。このために、アプリ ウィジェット アイテムの XML レイアウト ファイルにデータのソースを組み合わせます。このデータソースは、データベースでも単純な配列でも構いません。StackWidget のサンプルでは、データソースは WidgetItems の配列になっています。RemoteViewsFactory は、データをリモート コレクション ビューに結びつけるアダプタとして機能します。

RemoteViewsFactory サブクラスのために実装する必要がある特に重要な 2 つのメソッドは、onCreate()getViewAt() です。

初めてファクトリを作成するときに、システムは onCreate() を呼び出します。ここで、データソースへの接続やカーソルをセットアップします。たとえば、StackWidget のサンプルでは、onCreate() を使用して WidgetItemオブジェクトの配列を初期化します。アプリ ウィジェットが有効になると、システムが配列内のインデックス位置を使用してこのオブジェクトにアクセスし、オブジェクト内のテキストが表示されます。

StackWidget のサンプルから、onCreate() メソッドの位置を示す RemoteViewsFactory の実装を抜粋したコードを以下に示します。

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() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
            ...
        }
        ...
    }
    

Java

    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);
        }

        public void onCreate() {
            // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            for (int i = 0; i < 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 app 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 app 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);

        ...
        // Return the remote views object.
        return rv;
    }
    

個々のアイテムに動作を追加する

これまでのセクションでは、アプリ ウィジェットのコレクションにデータをバインドする方法を説明しました。ここでは、コレクション ビューの個々のアイテムに動的な動作を追加する場合について説明します。

AppWidgetProvider クラスを使用するで説明したように、通常は setOnClickPendingIntent() を使用してオブジェクトのクリック動作(ボタンに Activity を起動させるなど)を設定します。ただしこの手法は、個々のコレクション アイテム内の子ビューでは使用できません(たとえば、setOnClickPendingIntent() を使用して Gmail アプリ ウィジェットでアプリを起動するグローバル ボタンをセットアップすることはできますが、個々のリスト項目ではできません)。コレクション内の個々のアイテムにクリック動作を追加するには、setOnClickFillInIntent() を使用します。具体的には、コレクション ビューのためのペンディング インテント テンプレートをセットアップし、そのうえで RemoteViewsFactory を通じてコレクション内の各アイテムでフィルイン インテントを設定します。

このセクションでは、StackWidget のサンプルを使用して、個々のアイテムに動作を追加する方法について説明します。StackWidget のサンプルで、ユーザーが最前面のビューにタッチしたとき、アプリ ウィジェットは Toast メッセージ「ビュー n にタッチしました」を表示します(n はタッチされたビューのインデックス(位置)を示します)。この仕組みは以下のとおりです。

  • StackWidgetProviderAppWidgetProvider サブクラス)が、TOAST_ACTION と呼ばれるカスタム アクションを持つペンディング インテントを作成します。
  • ユーザーがビューにタッチしたときに、インテントが呼び出され、TOAST_ACTION をブロードキャストします。
  • このブロードキャストが StackWidgetProvideronReceive() メソッドによってインターセプトされ、アプリ ウィジェットがタッチされたビューについて Toast メッセージを表示します。コレクション アイテムのデータが RemoteViewsFactory によって RemoteViewsService 経由で提供されます。

注: StackWidget のサンプルではブロードキャストを使用していますが、このようなシナリオでは通常、アプリ ウィジェットは単にアクティビティを開始します。

ペンディング インテント テンプレートをセットアップする

ペンディング インテントをセットアップするのは 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 to see whether the intent's action is TOAST_ACTION. If it is, the app 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
                )
                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 app widgets with the remote adapter
            appWidgetIds.forEach { appWidgetId ->

                // Sets up the intent that points to the StackViewService that will
                // provide 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 we need to embed the extras
                    // into the data so that the extras will not be 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 should 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 cannot 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 will have 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 to see whether the intent's action is TOAST_ACTION. If it is, the app 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);
                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 app widgets with the remote adapter
            for (int i = 0; i < appWidgetIds.length; ++i) {

                // Sets up the intent that points to the StackViewService that will
                // provide 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 we need to embed the extras
                // into the data so that the extras will not be 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 should 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
                // cannot 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 will have 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() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result 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 app 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)

                // Next, set a fill-intent, which will be used 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() you set up any connections / cursors to your data source. Heavy lifting,
                // for example downloading or creating content etc, should be deferred to onDataSetChanged()
                // or getViewAt(). Taking more than 20 seconds in this call will result 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 app widget item XML file to construct a RemoteViews object.
            public RemoteViews getViewAt(int position) {
                // position will always range from 0 to getCount() - 1.

                // Construct a RemoteViews item based on the app 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);

                // Next, set a fill-intent, which will be used 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;
            }
        ...
        }
    

コレクション データを最新の状態に保つ

下の図は、コレクションを使用するアプリ ウィジェットで更新が発生したときに発生するフローを示したものです。アプリ ウィジェットのコードが RemoteViewsFactory とどのように通信するか、また更新をどのように開始できるかを示しています。

コレクションを使用するアプリ ウィジェットの特長として、ユーザーに最新のコンテンツを提供できることがあります。たとえば、Android 3.0 の Gmail アプリ ウィジェットでユーザーに受信トレイのスナップショットを提供する場合を考えてみましょう。それには、RemoteViewsFactory とコレクション ビューを起動して新しいデータを取得して表示させる必要があります。そのためには、AppWidgetManager を使用して notifyAppWidgetViewDataChanged() を呼び出します。この呼び出しの結果、RemoteViewsFactoryonDataSetChanged() メソッドへのコールバックが行われ、新しいデータを取得できるようになります。なお、onDataSetChanged() コールバックの中では、大量の処理が発生するオペレーションを同時に実行できます。RemoteViewsFactory からメタデータまたはビューデータが取得される前に、この呼び出しが完了することが保証されます。また、getViewAt() メソッドの中でも、大量の処理が発生するオペレーションを実行できます。この呼び出しに時間がかかる場合、応答があるまで、コレクション ビューに対応する位置に読み込み中のビュー(RemoteViewsFactorygetLoadingView() メソッドで指定されたもの)が表示されます。