Androidでの簡易なWidget作り方

Nexus5を買ったのでAndroidのウィジェットを作ってみます．
初Androidのためかなりシンプルなサンプルを作ります．

![widget.png](https://qiita-image-store.s3.amazonaws.com/0/32706/6be18fe9-8f2f-c66a-f66b-7062cb9c816d.png)

ボタンをクリックするとカウントアップするだけ．．．です．


## Androidのウィジェットとは
ウィジェットはAndroidの特徴でホームに色々貼り付けることができる，iPhoneとの大きな差異の1つで，APIのLevel3(Android1.5)から使えているそうです．今はAPI Level19(Android4.4)だからかなり初期から利用できているものです．

ウィジェットは次の手順で作ることができます．

1. ウィジェットの設定ファイル作成
2. ウィジェットのレイアウト作成
3. AppWidgetProviderのサブクラス作成
4. BroadcastReceiverのサブクラス作成
5. AndroidManifest修正


## 1. ウィジェットの設定ファイル作成
以下のようなXMLファイルを，ウィジェットの設定ファイルとして xml/demo_widget_provider.xml に定義します．

```xml:xml/demo_widget_provider.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="146dp"
    android:minWidth="292dp"
    android:previewImage="@drawable/widget"
    android:updatePeriodMillis="1000000" >
</appwidget-provider>
```

- initialLayoutはレイアウトファイルを設定．
- previewImageに画像を設定すると，ウィジェットの追加画面でサムネイルが表示可能．
- updatePeriodMills属性は，ウィジェットの更新タイマを定義します．このタイマに従ってAppWidgetProviderはonUpdateコールバックメソッドで呼ばれる(と書いてありますが，このサンプルでは時間を短くしても動作しなかったです)．

細かい点は[ガイドライン](http://developer.android.com/guide/practices/ui_guidelines/widget_design.html)に記載されているようです．


## 2. ウィジェットのレイアウト作成
ウィジェットは他のアクティビティと同じようにレイアウトを作ることができるますが，制限があります．

レイアウトは以下4つのみが利用可能です．

1. FrameLayout
2. LinearLayout
3. RelativeLayout
4. GridLayout


ウィジェットのクラスは以下をサポートしています．

1. AnalogClock
2. Button
3. Chronometer
4. ImageButton
5. ImageView
6. ProgressBar
7. TextView
8. ViewFlipper
9. ListView
10. GridView
11. StackView
12. AdapterViewFlipper

今回のサンプルでは以下のようなテキストフィールドとボタンだけのものを作ります．

```
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5sp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/title"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:maxLines="2"
        android:paddingBottom="5dp"
        android:textColor="#fcfcfc"
        android:textSize="16sp"
        android:textStyle="bold"
         />

    <Button
        android:id="@+id/button"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:text="click" />

</RelativeLayout>
```


## 3. AppWidgetProviderのサブクラス作成
ウィジェットをコントロールするため，AppWidgetProviderクラスを継承したクラスを追加します．
onUpdateメソッドが実行時に画面を変更するために使われます．
このクラスはウィジェットとのインターフェイスとなります．サンプルではonUpdateしか使っていませんが，ウィジェットの更新や有効・無効化・削除などのイベントを受けとることができるそうです．


```
public class MyWidgetProvider extends AppWidgetProvider {
	
	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
		// ウィジェットレイアウトの初期化
		RemoteViews remoteViews = new RemoteViews(context.getPackageName(),	R.layout.widget_layout);

		// ボタンイベントを登録
		remoteViews.setOnClickPendingIntent(R.id.button, clickButton(context));

		// テキストフィールドに"初期画面"と表示
		remoteViews.setTextViewText(R.id.title, "初期画面");

	    // アップデートメソッド呼び出し
		pushWidgetUpdate(context, remoteViews);
	}
	
	public static PendingIntent clickButton(Context context) {
		// クリック回数を増加
		MyWidgetIntentReceiver.clickCount ++;

		// initiate widget update request
		Intent intent = new Intent();
		intent.setAction("UPDATE_WIDGET");
		return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
	}

	// アップデート
	public static void pushWidgetUpdate(Context context, RemoteViews remoteViews) {
		ComponentName myWidget = new ComponentName(context, MyWidgetProvider.class);
		AppWidgetManager manager = AppWidgetManager.getInstance(context);
		manager.updateAppWidget(myWidget, remoteViews);
	}
}
```

## 4. BroadcastReceiverのサブクラス作成
ボタンクリック時のアクションを実現するためにBroadcastReceiverを実装します．
ウィジェットのをonReceiveメソッドで実現します．

```
public class MyWidgetIntentReceiver extends BroadcastReceiver {
	public static int clickCount = 0;

	@Override
	public void onReceive(Context context, Intent intent) {
		if (intent.getAction().equals("UPDATE_WIDGET")) {
			RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

			// テキストをクリック回数を元に更新
			remoteViews.setTextViewText(R.id.title, "クリック回数: " + MyWidgetIntentReceiver.clickCount);

			// もう一回クリックイベントを登録(毎回登録しないと上手く動かず)
			remoteViews.setOnClickPendingIntent(R.id.button, MyWidgetProvider.clickButton(context));

			MyWidgetProvider.pushWidgetUpdate(context.getApplicationContext(), remoteViews);
		}
	}
}
```

## 5. Androidマニフェストにreceiverを登録する
最後に，マニフェストファイルを記載して，ウィジェットを登録します．
(demo_widget_providerは，1で定義したXMLファイルです．)

```
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.torua.hellowidget"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
         <receiver android:name=".MyWidgetProvider" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/demo_widget_provider" />
        </receiver>
        <receiver
            android:name=".MyWidgetIntentReceiver"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="UPDATE_WIDGET" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/demo_widget_provider" />
        </receiver>
    </application>
</manifest>
```

動作の流れは以下の通りです．

- ウィジェット登録→MyWidgetProvier.onUpdate→描画
- ボタンクリック→インテント(UPDATE_WIDGET)→MyWidgetIntentReceiver.onReceive→描画


# 参考
- [App Widgets | Android Developers](http://developer.android.com/guide/topics/appwidgets/index.html)
- [App Widget Design Guidelines](http://developer.android.com/guide/practices/ui_guidelines/widget_design.html)
- [Homescreen App Widgets Example in Android](http://javatechig.com/android/app-widgets-example-in-android)