Android の Databinding で Spinner を使ったので、その方法をまとめておきます。
不満
Spinnerに調べていて思ったのですが、なんか使い勝手がものすごく悪くないですか?
公式を見てもらったらわかるのですが、固定の配列を下記のようにXMLに定義して読み込む感じになってるのですが、
こんなシチュエーションよりも、APIから取得してきたList配列を使って、もっと動的に配列の中身を変えるほうが多いのでは?と思うのですよ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="planets_array"> <item>Mercury</item> <item>Venus</item> <item>Earth</item> <item>Mars</item> <item>Jupiter</item> <item>Saturn</item> <item>Uranus</item> <item>Neptune</item> </string-array> </resources> |
例えばこれが国の一覧だとすると、200個以上 <item>Japan</item>
を書かないといけないわけです。ほんとにめんどくさいですねぇ。まぁ国の一覧なんて早々変更しないのであれですが、誤字脱字チェックなんてやりたくないですよね。
じゃあAPIから取得しようとなったときに、えっとListからはどうやってAdapterを作るの?となるわけで、また調査。めんどくさいですねぇ。
調べた結果、xmlからでなく、ListからAdapterを作成する方法が下記です。
1 2 3 |
List<String> items = countryRepository.getContries(); ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, items); |
ArrayAdapterのコンストラクタで、ちゃんとリストからの生成方法が用意されていたわけです。
Databindingで使うためには
上記のはちょっとした愚痴です。忘れてください。
さて本題です。
まず、Databindingを使う上で何を考えないといけないかというと、BaseObservableを継承したviewModelから、なんの情報をxmlにBindしたいのか?です。
ここでは、頻繁にSpinner内の内容が変わっていくユースケースを想定して、
- 先程のList
をBindすることを考えます。
それともう一つ、考えたいのが、
- ユーザーがSpinner選択したときのコールバックをviewModelの内部で受け取れるようにする
とMVVMが実現できてよさそうです。
解答
上記を実現するために、下記のようにしてみました。
activity_something.xml
1 2 3 4 5 6 7 8 9 |
<Spinner android:id="@+id/spinner_number" android:layout_width="200dp" android:layout_height="wrap_content" android:spinnerMode="dialog" android:onItemSelected="@{(parent, view, position, id) -> viewModel.onItemSelectedNumber(parent, view, position, id)}" android:entries="@{viewModel.numberList}" /> |
SomethingViewModel.java
1 2 3 4 5 6 |
public void onItemSelectedNumber(AdapterView<?> parent, View view, int position, long id) { setNumber(numberList.get(position)); //Call Sip Register here } |
まず、一つ目のリストを動的に渡すやり方ですが、xml 内のandroid:entries
にviewModelからListを渡しています。個人的にはちゃんと動くのか自信がなかったのですが、内部でうまくCharSequence[]
へ変換して、Adapterへの設定もやってくれます。ただ、スピナのデザインなどを変えるとなると、Custom Setters を使ってやるしかないかなと思います。
続いてコールバックについて。
コールバック部分はAdapterViewBindingAdapter
というクラスにandroid:onItemSelected
が用意されているので、それにたいして、ラムダ式で設定します。
スピナーのsetOnItemSelectedListener
には、AdapterView.OnItemSelectedListener()
を渡す必要があるのですが、このリスナーは下記のように2つのメソッドを持っているので、そのままではxmlに渡すことができません。そこで、上記のBindingAdapterを使うことで、それぞれ別々にリスナーを設定することを可能にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { } @Override public void onNothingSelected(AdapterView<?> parent) { } }); |
ちなみに、よく似た名前で、setOnItemClickListener
のがあるのですが、 これを使うとRuntimeException
になるようです。サポーしてないからってException投げるってどうなんでしょう。Lintでエラー警告だすとかできないものでしょうか。
まとめ
スピナーってあまり使う機会がないようで、たまーに使いますよね。そんなときにはぜひこの方法で楽に実装してもらえたらと思います!