ページ

2011/05/03

Android アノテーションでRadioButtonの値をコード値として取得

|
Androidのラジオボタンは、HTMLのラジオボタンのようなvalueを持っていません。
「どのRadioButtonが選択されたか」と「選択されたRadioButtonのテキスト」くらいしか
そのラジオボタンを特定する情報がありません。

そのため、ラジオボタンの値をDBへ登録しようなどと考えていると
リソースIDとコード値の割り当てをハードコーディングすることになってしまいます。

例えば以下のような感じです。
RadioGroup radioGroup = (RadioGroup) activity.findViewById(R.id.radio_group_foo);
int checkedId = radioGroup.getCheckedRadioButtonId();
int value;
switch (checkedId) {
case R.id.radio_button_foo_1:
 value = 1;
 break;
case R.id.radio_button_foo_2:
 value = 2;
 break;
} 

このマッピングを色んなところに散らかすのは嫌です。

AndroidのUIに関するプログラミングである以上、
リソース(R.java)を扱うのは避けられそうにないので、外部の設定ファイルにマッピングを書くのは難しそうです。
そこで、アノテーションを使ってラジオボタンの値を設定するフィールドに情報をまとめることにしました。

ソースコードにマッピングを書くのは変わりませんが、
情報がまとまるのでコードは見やすくなるはずです。
イメージは、SAStrutsのような、アノテーションをpublicフィールドに定義する感じです。

以下、必要なアノテーションです。

Radio.java
@Target(value = { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Radio {
    int groupId();
    RadioValue[] values();
}

RadioValue.java
@Target(value = { ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RadioValue {
    int id();
    String value();
}

ラジオボタンの以下のような定義があったとします。
<RadioGroup
 android:id="@+id/type"
 android:orientation="horizontal"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content">
 <RadioButton
  android:id="@+id/type_a"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:checked="true"
  android:text="タイプA" />
 <RadioButton
  android:id="@+id/type_b"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="タイプB" />
</RadioGroup>

このラジオボタンの値を格納するフィールドをアノテーションを使って定義する例です。
@Radio(groupId = R.id.type,
       values = { @RadioValue(id = R.id.type_a, value = "A"),
                  @RadioValue(id = R.id.type_b, value = "B") })
public String type;

アノテーションを使ってフィールドに値を取得するロジックです。(断片)
Field field = target.getClass().getField("type");
:
Radio radio = (Radio) field.getAnnotation(Radio.class);
if (radio != null && type.equals(String.class)) {
    int groupId = radio.groupId();
    RadioGroup radioGroup = (RadioGroup) activity.findViewById(groupId);
    int checkedId = radioGroup.getCheckedRadioButtonId();
    RadioValue[] values = radio.values();
    for (int i = 0; i < values.length; i++) {
        if (values[i].id() == checkedId) {
            field.set(form, values[i].value());
            break;
        }
    }
}

これで、選択されたラジオボタンに応じて
「A」や「B」などの値がtypeというフィールドに取得できます。

フィールド名をハードコーディングしているので、
このままだとフィールド数分最後のロジックを書くことになってしまいますが、
フィールドの取得部分(getField)も「クラス内の全てのpublicフィールドが対象」
などとしてしまえばだいぶ汎用的になるはずです。

注意:
上記のアノテーションRadioValueを定義せず、
Radio内にリソースIDの配列(int[] ids())と値の配列(String[] values())
を定義する方法も考えられますが、以下のAndroidのバグがあるためAndroid2.1以前では動作しません。
Issue 11124: array of primitive types in annotation
プリミティブ型配列のアノテーションパラメータの実装が漏れていたようです。

0 件のコメント: