【Android】初めてSpinnerを使ってみる

今回は、初めてAndroidのSpinnerを使う機会があったので、その使い方についてまとめてみました。
Spinnerとは画像のように複数の選択肢をドロップダウンで表示する画面部品のことです。

<img width="400px" alt="Screenshot_20211105-164307.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1261074/a0b8d624-ca8b-7282-38b5-34fe1f0898cf.png">

## 環境
kotlin: 1.5.31
Android Studio: Android Studio Arctic Fox 2020.3.1 Patch3

## 基本的な使い方
### Spinnerの作成
まずはViewにSpinnerを作成します。
SpinnerはAppCompatSpinnerの部分です。

``` activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/light_blue"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textDesc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp"
        android:text="目的地を選択してください。"
        android:textColor="@color/black"
        android:textSize="16sp" />

    <androidx.appcompat.widget.AppCompatSpinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/buttonSetting"
        android:layout_width="240dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="24dp"
        android:text="設定" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textSelected"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp"
        android:text="選択された目的地：" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/buttonReset"
        android:layout_width="240dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="24dp"
        android:text="選択をリセット" />

</androidx.appcompat.widget.LinearLayoutCompat>
```

Viewを表示すると以下のようになります。

<img width="400px" alt="Screenshot_20211105-164036.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1261074/4ee8940a-503e-9dfc-1cce-728a1fcfa854.png">

### Adapterの作成
次にSpinnerにセットするAdapterをActivityで作成します。
adapterにはArrayAdapterを使用し、コンストラクタにはContxt、選択肢に使用するレイアウトのリソース、選択肢のデータの順でセットします。

``` MainActivity.kt
val array = arrayOf("未選択", "大阪", "名古屋", "東京")
val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, array)
spinner.adapter = arrayAdapter

```

選択肢のデータには配列やリストを使用できます。
選択肢のレイアウトにはAndroidに標準で備わっているものを使用しています。
もし文字色等の見た目を変更したい場合は自分で作ったレイアウトを代わりに使うことも可能です。

なお、Spinnerのadapterのデータ型はSpinnerAdapter型(インターフェース型)ですがArrayAdapterはSpinnerAdapterを実装していますのでそのままセットできます。

### 選択されたアイテムを取得
ここで「設定」ボタンをタップしたときに選択されたアイテムを取得してビューに表示してみます。
選択されたアイテムは「spinner.selectedItem」で取得し、String型に変換します。

``` MainActivity.kt
buttonSetting.setOnClickListener {
    val item = spinner.selectedItem.toString()
    textSelected.text = "選択された目的地： $item"
}
```

### 選択されたアイテムの変更を検知する
SpinnerのアダプターにOnItemSelectedListenerを実装すると選択されたアイテムの変更を検知して、それに基づいた処理を行うことが可能です。

``` MainActivity.kt
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
  override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
      Log.e("onItemSelected", "parent: $parent, view: $view, pos: $pos, id: $id")
      if (pos == 0) {
          buttonSetting.isEnabled = false
          textSelected.text = "選択された目的地："
      } else {
          buttonSetting.isEnabled = true
      }
  }

  override fun onNothingSelected(parent: AdapterView<*>?) {
      Log.e("onNothingSelected", "parent: $parent")
  }
}
```

上記のコードでは「未選択」が選択されたときだけ「設定」ボタンを無効化して「選択された目的地」のitem表示部分を空欄にしています。
「pos」は選択したアイテムのポジションであり、その番号はセットしたデータ(配列)のインデックス番号と一致します。
ちなみに、onNothingSelectedはSpinnerの選択が何らかの方法で解除されたときに実行されるメソッドです。(利用機会は少ないと思われます。)

### Activityから任意の選択肢を選択する
setSelection()で選択肢のpositionを指定します。

``` MainActivity.kt
buttonReset.setOnClickListener {
    spinner.setSelection(0)
}
```

「選択をリセット」ボタンをタップしたときに選択肢が「未選択」に戻ります。


ここまでのActivityのコードは以下になります。

``` MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // adapter作成
        val array = arrayOf("未選択", "大阪", "名古屋", "東京")
        val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, array)
        spinner.adapter = arrayAdapter

        // 選択されたアイテムの変更を検知する
        spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
                Log.e("onItemSelected", "parent: $parent, view: $view, pos: $pos, id: $id")
                if (pos == 0) {
                    buttonSetting.isEnabled = false
                    textSelected.text = "選択された目的地："
                } else {
                    buttonSetting.isEnabled = true
                }
            }

            override fun onNothingSelected(parent: AdapterView<*>?) {
                Log.e("onNothingSelected", "parent: $parent")
            }
        }

        // 選択されたアイテムを取得
        buttonSetting.setOnClickListener {
            val item = spinner.selectedItem.toString()
            textSelected.text = "選択された目的地： $item"
        }

        // 選択をリセット
        buttonReset.setOnClickListener {
            spinner.setSelection(0)
        }
    }
}
```

## 簡潔に書く
リソースファイルに配列を定義するとActivityでadapterを作成する必要がなくなるため、Activityのコードを少しだけ簡潔に書くことができます。

``` strings.xml
<resources>
    <string name="app_name">SpinnerProject</string>
    <string-array name="spinner_items">
        <item>未選択</item>
        <item>大阪</item>
        <item>名古屋</item>
        <item>東京</item>
    </string-array>
</resources>
```

作成した配列はレイアウトファイルのSpinnerタグのentries属性に設定します。

``` activity_main.xml
 <androidx.appcompat.widget.AppCompatSpinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:entries="@array/spinner_items"
        android:layout_marginTop="24dp"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="24dp" />
```

これでSpinnerに配列がセットされました。

Activityのコードは以下になります。

``` MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 選択されたアイテムの変更を検知する
        spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
                Log.e("onItemSelected", "parent: $parent, view: $view, pos: $pos, id: $id")
                if (pos == 0) {
                    buttonSetting.isEnabled = false
                    textSelected.text = "選択された目的地："
                } else {
                    buttonSetting.isEnabled = true
                }
            }

            override fun onNothingSelected(parent: AdapterView<*>?) {
                Log.e("onNothingSelected", "parent: $parent")
            }
        }

        // 選択されたアイテムを取得
        buttonSetting.setOnClickListener {
            val item = spinner.selectedItem.toString()
            textSelected.text = "選択された目的地： $item"
        }

        // 選択をリセット
        buttonReset.setOnClickListener {
            spinner.setSelection(0)
        }
    }
}
```

## 参考
[[ Android ] プルダウンの実装方法 - Qiita](https://qiita.com/QiitaD/items/e7b7ed95d0abaa106337)
[Android studio(Java)でspinnerで文字配列を表示するソース - Qiita](https://qiita.com/ktakeda/items/4f8c17120660e3411611)