【Android Data Binding】ListView に ObservableArrayList をバインドする
Android Data Binding で ListView にリストデータ(ObservableArrayList)をバインドできないかと調べてみたときの備忘録。
はじめに
例えば TextView に ObservableField<String> をバインドしたい場合、ソースコードは以下のようになります。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewModel" type="com.nagochi.ViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> </LinearLayout> </layout>
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.setViewModel(new ViewModel()); } }
ViewModel.java
public class ViewModel { public final ObservableField<String> text = new ObservableField<>("テキスト"); }
これと同じように ListView にも ObservableArrayList<String> をバインドしたいのですが、標準ではそのような機能は付いていません。ListView をカスタムして自分で実装する必要があります。
というわけで、ListView を継承したクラス BindingListView を作っていこうと思います。最終的な目標は以下のような書き方でデータバインドが可能になることです。
<BindingListView android:id="@+id/bindingListView" android:layout_width="match_parent" android:layout_height="match_parent" app:list="@{viewModel.list}" />
public class ViewModel { public final ObservableArrayList<String> list = new ObservableArrayList<>(); }
自動セッター setList を定義する
まずは xml にapp:list=“@{viewModel.list}”
という式を記述できるようにします。これは簡単で、クラス内に自動セッターとして setList メソッドを作ればいいだけです。
public class BindingListView extends ListView { public BindingListView(Context context, AttributeSet attrs) { super(context, attrs); } public void setList(ArrayList<String> list) { } }
Android Data Binding の機能の一つです。クラス内に自動セッターpublic void setxxx(引数yyy)
を定義しておくと、 xml にてapp:xxx="@{引数yyy}"
という式を記述できるようになります。
データ バインディング ライブラリ | Android Developers #自動セッター
それでは xml にapp:list=“@{viewModel.list}”
という式を記述しておくと何が起こるのか?ですが、viewModel.list にデータを追加するたびに setList が呼び出されるようになります。ですのでデータの追加時にするべき処理がある場合は、ここに記述すればいいということになります。
setList の中身を記述する
ということで、データの追加時にする処理を setList に記述していきます。
public class BindingListView extends ListView { private ArrayAdapter<String> adapter; public BindingListView(Context context, AttributeSet attrs) { super(context, attrs); } public void setList(ArrayList<String> list) { if (adapter == null) { adapter = new ArrayAdapter<>( getContext(), android.R.layout.simple_list_item_1, list); setAdapter(adapter); } adapter.notifyDataSetChanged(); } }
まずひとつ目は Adapter の作成です。送られてきた list を画面に表示させるためには、通常の ListView を扱うときもそうですが Adapter を作成する必要があります。Adapter は一度だけ作ればいいので、setList の初回呼び出し時のみ Adapter を作成します。
ふたつ目は ListView の表示更新です。すでに list にデータは追加されていますが、それだけでは画面に反映されません。adapter.notifyDataSetChanged();
を実行し、 ListView の表示更新を促す必要があります。
以上で実装は完了です。
ソースコード全体
最低限のコードの他に、動作確認のために viewmodel.list に5秒毎にデータを追加するコードも記述しています。画像からでは分かりにくいですが、viewmodel.list にデータを追加した瞬間に ListView も更新され、項目が増えていることが確認できます。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewModel" type="com.nagochi.ViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.nagochi.BindingListView android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="wrap_content" app:list="@{viewModel.list}" /> </LinearLayout> </layout>
MainActivity.java
package com.nagochi; import android.databinding.DataBindingUtil; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.nagochi.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.setViewModel(new ViewModel()); } }
ViewModel.java
package com.nagochi; import android.databinding.ObservableArrayList; import android.os.Handler; import android.os.Looper; public class ViewModel { public final ObservableArrayList<String> list = new ObservableArrayList<>(); public ViewModel() { list.add("データ1"); list.add("データ2"); list.add("データ3"); timer(); } private void timer() { new Handler(Looper.getMainLooper()).postDelayed(() -> { list.add("追加データ"); timer(); }, 5000); } }
BindingListView.java
package com.nagochi; import android.content.Context; import android.util.AttributeSet; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; public class BindingListView extends ListView { private ArrayAdapter<String> adapter; public BindingListView(Context context, AttributeSet attrs) { super(context, attrs); } public void setList(ArrayList<String> list) { if (adapter == null) { adapter = new ArrayAdapter<>( getContext(), android.R.layout.simple_list_item_1, list); setAdapter(adapter); } adapter.notifyDataSetChanged(); } }