はじめに
リスト表示でDataBindingを使う方法についてまとめます。
ListViewで一覧を表示するだけでなく、追加や削除した結果を動的に反映する方法も紹介します。
Step0. 準備
例として、日時の文字列の一覧を表示するサンプルを作ります。
生成されたときの日時を文字列で表示します。
public class DateTime {
private String dateTime;
public DateTime() {
this.dateTime = new Date().toString();
}
public String getName() {
return dateTime;
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="dateTime" type="com.example.databindinglistview.DateTime" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:text="@{dateTime.name}"
/>
</RelativeLayout>
</layout>
Step1. Adapter作成
リストの子要素を管理するAdapterを作成します。
Viewとの紐づけにDataBindingを用いることでViewHolderが不要になり、以下のようにかなりスッキリ書けます。
public class DateTimeAdapter extends ArrayAdapter<DateTime>{
public DateTimeAdapter(Context context, ArrayList<DateTime> dateTimeList) {
super(context, 0, dateTimeList);
}
@NonNull
@Override
public View getView(final int position, View convertView, @NonNull ViewGroup parent) {
ViewRowDateTimeListBinding binding;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
binding = DataBindingUtil.inflate(inflater, R.layout.view_row_date_time_list, parent, false);
convertView = binding.getRoot();
convertView.setTag(binding);
} else {
binding = (ViewRowDateTimeListBinding) convertView.getTag();
}
binding.setDateTime(getItem(position));
return binding.getRoot();
}
}
Step2. リストデータをセットする
xml上でListを渡すために、ListViewを拡張します。
setList(...)
を新たに追加して、中でListからAdapterを作っているのですが、このsetList
は自動セッターという仕組みでxmlからlist
属性として見えるようになります。
https://developer.android.com/topic/libraries/data-binding/index.html?hl=ja#automatic_setters
public class DateTimeListView extends ListView{
DateTimeAdapter adapter;
public DateTimeListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setList(List<DateTime> dateTimeList){
Log.d("DEBUG", "setList is called");
if (getAdapter() == null) {
adapter = new DateTimeAdapter(getContext(), dateTimeList);
setAdapter(adapter);
}
}
}
(参考)ListViewをいじらず、BindingAdapterでカスタムセッターを用意する手段もアリです。
@BindingAdapter("list")
public static void setList(ListView listView, List<DateTime> dateTimeList) {
Log.d("DEBUG", "setList is called");
if (listView.getAdapter() == null) {
DateTimeAdapter adapter = new DateTimeAdapter(listView.getContext(), dateTimeList);
listView.setAdapter(adapter);
}
}
拡張したListViewをActivityのレイアウトに配置して、先程定義したlist
属性にリストデータを入れます。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="java.util.List" />
<import type="com.example.databindinglistview.DateTime"/>
<variable name="dateTimeList" type="List<DateTime>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.example.databindinglistview.DateTimeListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="false"
app:list="@{dateTimeList}"
/>
</LinearLayout>
</layout>
DataBindingでList<>を渡すときは、エスケープシーケンスを使ってList<DateTime>
のように書きます。
あとは、ActivityでBindingにデータをセットすればListViewに表示されます。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
List<DateTime> dateTimeList = new ArrayList<>();
dateTimeList.add(new DateTime());
dateTimeList.add(new DateTime());
dateTimeList.add(new DateTime());
binding.setDateTimeList(dateTimeList);
}
Step3. アイテムのクリックイベントを拾う
イベントを受け取るHandlerを用意します。
引数はAdapterView.OnItemClickListener
のonItemClick
に合わせておきます。
public interface MainEventHandler {
void onItemClick(AdapterView<?> parent, View view, int position, long id);
}
あとはListViewで以下のようにセットすれば、子要素をクリックしたときのイベントを受け取ることができます。
<data>
...
<variable name="handlers" type="MainEventHandler" />
</data>
...
<com.example.databindinglistview.DateTimeListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="false"
app:list="@{dateTimeList}"
android:onItemClickListener="@{handlers.onItemClick}"
/>
Step4. リストの追加や削除を動的に反映させる
上記で単にリストに要素を表示するだけのものができました。
しかし、このままではリストに追加や削除があっても反映されません。
そこで今回は、リストの変更を通知できるObservableArrayListを使ってみることにしました。
Adapterを直接触らずにListViewを更新できるようになるので便利です。
//List<DateTime> dateTimeList = new ArrayList<>();
ObservableArrayList<DateTime> dateTimeList = new ObservableArrayList<>();
ほか、関連する部分もList<DateTime>
からObservableArrayList<DateTime>
に書き換えます。
あとは、ListViewでlist更新時にnotifyDataSetChanged()
を呼んでおきます。
public void setList(List<DateTime> dateTimeList){
...
adapter.notifyDataSetChanged();
}
長くなるのでソースは割愛しますが、この置き換えだけでリストの追加削除時にDataBindingが変更を検知して、setList(...)
、notifyDataSetChanged()
が呼ばれ、ListViewの表示が直ちに更新されるようになります。
リストの追加削除を含むソース全体はこちら
おわりに
ListViewでDataBindingを使うとAdapter周りのソースをかなりシンプルにすることができました。
さらに要素をObservableArrayList<>にすることで、元のデータを操作するだけでListViewが自動的に更新されるようになり、呼び出し側はAdapterさえ触らずに済むようにできました。