PendingIntentの処理
PendingIntentは基本的に、IntentとstartActivity()やbroadcastIntent()などのターゲットアクションを組み合わせたラッパーオブジェクトです。前回の記事では、App Widgetに表示画面の変更を開始させるアラーム通知のために、PendingIntentを使いました。前回説明したとおり、App Widgetのインスタンスは複数存在する可能性があるので(図1では3つあります)、アプリケーションからそれぞれのインスタンスを区別できるようにするには、Intentが固有である必要があります。これを実現するにはUriフィールドにApp Widget IDを指定します。これによって、それぞれのApp Widgetに固有の時間間隔を指定できるようになります。
以下のコードは、これと同じ方法でconfigIntentを作成し、時間遅延Activityを起動させます。それぞれのappWidgetIdでそれぞれのApp Widgetが起動されます。
Intent configIntent =
new Intent(context, ImagesWidgetConfiguration.class);
configIntent.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
configIntent.setData(Uri.withAppendedPath(Uri.Parse
(ImagesWidgetProvider.URI_SCHEME + "://widget/id/"),
String.valueOf(appWidgetId)));
これで設定ボタンが動作するようになったので、今度は、次の画像へのスキップなどの別のボタンバー機能を追加します。次の画像へスキップするようRemoteViewsコントロールへ通知するには、App Widgetにステートの変更をブロードキャストします。
一見、ボタンハンドラへ送信されるIntentの一部としてステート情報をエンコードすればいいように思えます。しかし、このApp Widgetは、Alarmから送信されるIntentによって、定期的間隔でも更新を行うことを忘れてはいけません。このIntentは変化せず、新しいステートを認識しません。この問題を解決するために、ACTION_WIDGET_CONTROLという新しいアクションタイプを作成し、IntentのUriにコマンドを追加します。次に、App WidgetのステートをSharedPreferencesに保存します。実際には、一般的なApp Widget実装の構造は単純なので、この処理でユーザーが感知するほどの遅延は発生しません。
以下のヘルパーメソッドでは、新しいアクションタイプACTION_WIDGET_CONTROLを処理するPendingIntentを作成しています。
private PendingIntent makeControlPendingIntent
(Context context, String command, int appWidgetId) {
Intent active = new Intent();
active.setAction(ACTION_WIDGET_CONTROL);
active.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
Uri data = Uri.withAppendedPath(
Uri.parse(URI_SCHEME + "://widget/id/#"+command),
String.valueOf(appWidgetId));
active.setData(data);
return(PendingIntent.getBroadcast(context,
0, active, PendingIntent.FLAG_UPDATE_CURRENT));
}
このコードブロックは、アクションACTION_WIDGET_CONTROL(単なる文字列)を持つ新しいIntentを作成します。次に、IntentのExtra値をApp Widgetの固有IDに設定します。それから固有のUriを作成し、目的のコマンドを追加します。これでこのIntentは、App WidgetホストからAndroidシステムにブロードキャストできるようになりました。このブロードキャストは、普通のApp Widgetコマンドを処理するのと同じブロードキャスト受信先で処理されます。
App Widgetは、これらのブロードキャストをonReceive()ハンドラメソッド内で受信できます。
else if (ACTION_WIDGET_CONTROL.equals(action)) {
final int appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId !=
AppWidgetManager.INVALID_APPWIDGET_ID) {
this.onHandleAction(
context, appWidgetId, intent.getData());
}
当該アクションの着信ブロードキャストを処理するには、この新しいアクションタイプと比較します。一致した場合、対象App Widgetインスタンスにコマンドを送信します。
ステートの処理
ユーザー操作に基づいてIntentオブジェクトをApp Widgetに渡す方法、およびそれを検出する方法がわかったので、今度は要求された操作を処理します。これには次のタスクを実行します。
- 対象のRemoteViewsインスタンスを取得する
- そのステートを取得して変更する
- それらの変更をRemoteViewsコントロールに反映する
- 対象のApp Widgetに変更を反映する
updateAppWidget()メソッドを呼び出す
これらの各タスクを処理するコードの例は、この記事のダウンロード可能なソースコードのonHandleAction()メソッドとupdateDisplayState()メソッドにあります。
RemoteViewsコントロールは、更新されるたびに元のレイアウトから再作成されています。そこで今度はクリックハンドラを使用します。App Widgetの現在のステートに基づき、前に設定Activityアクションを追加したのと同じようにして、必要なクリックハンドラを追加します。これらのハンドラのコードも、ソースコードのupdateDisplayState()メソッドで確認できます。
レイアウトの更新
App Widgetの下部にボタンバーが描画されるようにするには、2つのFrameLayoutコントロールを使用します。外側のFrameLayout(このApp Widget全体を占めるレイアウト)に、表示するImageViewと、ボタンバー用のもう1つのFrameLayoutを含めます。このもう1つのFrameLayoutは親レイアウトの下部に描画されるようにし、そこに、設定、再生/停止、次の画像、の3つのImageButtonコントロールを配置します。このボタンバー用FrameLayoutは、可視属性をGONEに設定すればビューから非表示にできます。完成したレイアウトファイル(widget.xml)は、この記事で提供するサンプルコードで確認できます。
Androidマニフェストファイルの更新
最後に、この新しいアクションタイプを処理できるということをシステムに知らせるため、AndroidManifest.xmlファイルを更新して適切なインテントフィルタを追加します。
<receiver android:name=".ImagesWidgetProvider">
<intent-filter>
<action android:name=
"com.mamlambo.ImagesWidget.WIDGET_CONTROL" />
<data android:scheme="images_widget" />
</intent-filter>
</receiver>
このインテントフィルタは、アクションタイプがACTION_WIDGET_CONTROLであるブロードキャストIntentをウォッチするようにアプリケーションに指示します。その値が"com.mamlambo.ImagesWidget.WIDGET_CONTROL"です。
まとめ
App Widgetは、Androidユーザーにまったく新しい体験を提供します。静的なApp Widgetだけでなく、クリックなどのユーザーイベントに反応するインタラクティブ機能を備えたApp Widgetも作成できます。この記事では、AndroidのApp Widgetにユーザー操作処理を追加する方法を紹介しました。これに伴い、PendingIntentを使ってRemoteViewsとやり取りする方法も説明しました。最後に、Intentメカニズムの制約とその回避方法を説明して、ユーザーがホーム画面から離れずに操作できるApp Widgetを作成する方法を解説しました。
パート1「Androidのホーム画面用App Widgetを作成する(原文)」も併せて参照ください。
