DataBindingを使ってSpinnerの選択位置を得たり操作したりしたい


2017/2/3 更新

コメントでご教授いただいたとおり、android:selectedItemPositionという定義済みattributeが既に存在していましたのでそちらを利用するよう改めました。
参考：[Android data bindingで定義済みの属性リスト - Qiita](http://qiita.com/kobakei/items/5e902dd24005e6768ac4)

---

選択肢としてのテキストではなく選択位置に応じたコード値を取得したいとか、別の処理に応じてコード側からSpinnerの選択位置を操作したいというようなことってギョームアプリではよくあることだと思います。

今まではActivityなりFragmentなりにゴリゴリ処理を書いていましたが、DataBindingを導入してもうちょっと効率よくできないかと考えてみました。

# コード

## BindingAdapter

AdapterView.selectedItemPositionを利用しますので、TextViewのandroid:text attributeにStringをバインドできるようにするための記述を加えるだけです。

```java
public class DataBindingAdapter {
    @BindingAdapter("android:text")
    public static void setTextViewText(TextView textView, String text) {
        if (text != null) {
            textView.setText(text);
        } else {
            textView.setText("");
        }
    }
}
```

## ViewModel

今回はサンプルのためViewModelだけ定義しています。

```java
public class MainViewModel extends BaseObservable {
    private final String[] values;
    private final View.OnClickListener onClickListener;

    private String selectedText;
    private int selectedPosition;

    public MainViewModel(String[] values) {
        this.values = values;
        this.onClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setSelectedPosition(0);
            }
        };
    }

    public View.OnClickListener getOnClickListener() {
        return onClickListener;
    }

    @Bindable
    public String getSelectedText() {
        return selectedText;
    }

    @Bindable
    public int getSelectedPosition() {
        return selectedPosition;
    }

    public void setSelectedText(String selectedText) {
        this.selectedText = selectedText;
        notifyPropertyChanged(BR.selectedText);
    }

    public void setSelectedPosition(int selectedPosition) {
        this.selectedPosition = selectedPosition;
        notifyPropertyChanged(BR.selectedPosition);
        setSelectedText(this.values[selectedPosition]);
    }
}
```

## レイアウト

activity_main.xmlのレイアウトはこんな感じです。Spinnerの選択肢に応じたテキストをTextViewに表示するのと、Buttenを押したらSpinnerの選択位置を先頭にリセットするのが目標です。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <data>
        <variable
            name="viewModel"
            type="com.example.trs.spinnerbindingsample.MainViewModel"/>
    </data>
    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.trs.spinnerbindingsample.MainActivity">
        <Spinner
            android:id="@+id/spinner"
            android:layout_alignParentTop="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:entries="@array/spinner_code"
            android:selectedItemPosition="@={viewModel.selectedPosition}"
            />
        <TextView
            android:id="@+id/text"
            android:layout_below="@id/spinner"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:text="@{viewModel.selectedText}"
            />
        <Button
            android:layout_below="@id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="リセット"
            android:onClick="@{viewModel.onClickListener}"
            />
    </RelativeLayout>
</layout>
```

MainActivity.javaはDataBindingを定義するだけです。

```java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    MainViewModel viewModel = new MainViewModel(getResources().getStringArray(R.array.spinner_value));
    binding.setViewModel(viewModel);
}
```

## リソース

Spinnerで表示するためのstring-arrayとそれに応じたテキストを収めたstring-arrayを定義します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="spinner_code">
        <item>我が名はブリュンヒルデ！</item>
        <item>闇に飲まれよ！</item>
        <item>煩わしい太陽ね！</item>
        <item>我が友よ！</item>
        <item>我に力を！</item>
    </string-array>
    <string-array name="spinner_value">
        <item>神崎蘭子です！</item>
        <item>お疲れ様です！</item>
        <item>おはようございます！</item>
        <item>プロデューサーさん！</item>
        <item>レッスンお願いします！</item>
    </string-array>
</resources>
```

# 実行

![初期状態](https://qiita-image-store.s3.amazonaws.com/0/21354/f7ea83ea-a9d6-7a91-6e67-0043d36eed2b.png)

![Spinner展開して選択](https://qiita-image-store.s3.amazonaws.com/0/21354/92b2978c-6211-b653-9a56-4809ee9f7181.png)

![選択後](https://qiita-image-store.s3.amazonaws.com/0/21354/9c3deee2-adc8-3d79-6a07-17ae99d3cb6e.png)

# 参考

[java - Android spinner Data Binding using XML and show the selected values - Stack Overflow](http://stackoverflow.com/questions/37874091/android-spinner-data-binding-using-xml-and-show-the-selected-values)

[Android BindingAdapter order of execution? - Stack Overflow](http://stackoverflow.com/questions/40932933/android-bindingadapter-order-of-execution)

[今更遅れてDataBinding事始め(3.7) - exception think](http://exception-think.hatenablog.com/entry/20161118/1479479400)