はじめに
いまさらですが、DataBindingを使いだしたので、数回に分けてまとめていこうと思います。
findViewByIdとかButterKnifeの代替としてよりも、Viewとロジックを分離するような用途を想定しています。
今回は、DataBindingの導入からオブジェクトのプロパティの変更を監視してViewに反映するまでです。
Step0. 導入
appのgradleに以下を追加
android{
...
dataBinding {
enabled = true
}
}
以上
Step1. まずはオブジェクトの値を表示してみる
バインドするクラスを定義
例として、以下のようなUserクラスを用意しました。
public class User{
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
レイアウトファイル
xmlファイルのルートを<layout>
にして、使用するオブジェクトを定義する<data>
と、ビューを記述します。
例では、Userオブジェクトを"user"(任意)という名前で定義しています。
Viewからバインドオブジェクトのプロパティにアクセスするには、android:text="@{user.name}"
のように、@{}
でくくって記述します。
UserにgetName()
の定義が無く、nameフィールドがpublicの場合はnameの値が適用されます。
また、@{user.getName}
でもgetName()
が呼ばれ、この場合は定義がなければビルドエラーとなります。
なお、@{}
内はnullを許容するようになっており、userがnullでも特にNullPointerExceptionは発生しません。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Binding Objects -->
<data>
<variable name="user" type="com.example.databinding.User" />
</data>
<!-- Views -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
</LinearLayout>
</layout>
オブジェクトをバインド
以下のように<layout>
でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られます。
例:
activity_main.xml => ActivityMainBinding
fragment_sample.xml => FragmentSampleBinding
Bindingのインスタンスを得るには、Activity, Fragmentでそれぞれ以下のようにし、setUser()
でxmlのuserとmUserを紐付けます。
Activityの場合
private User mUser = new User("Taro");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setUser(mUser); //activity_main.xmlのuserにセットされる
binding.setHandlers(this);
}
Fragmentの場合
private User mUser = new User("Taro");
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentSampleBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_sample, container, false);
binding.setUser(mUser); //fragment_sample.xmlのuserにセットされる
return binding.getRoot();
}
これでビルドして実行すると画面に"Taro"と表示されます。
Step2. ビューで発生したイベントを受け取る
Step2, Step3ではボタンでユーザー名を動的に変更する機能を追加してみます。
イベントハンドラの定義
先程はレイアウトの<variable>
にUserオブジェクトを指定しましたが、イベントハンドラを登録することもできます。ここではユーザー名を変更するイベントを持つインタフェースを用意して設定してみます。
public interface SampleEventHandlers {
void onChangeClick(View view);
}
onChangeClick
の引数は、クリックイベントと対応づけるためにView.OnClickListener
のonClick
と同じ(View view)
にしてください。(*1)
これをUserと同様に<variable>
に記述することで、各Viewから利用できるようになります。
<data>
<variable name="user" type="com.example.databinding.User" />
<variable name="handlers" type="com.example.databinding.SampleEventHandlers" />
</data>
クリックイベントと紐付け
ButtonのonClickイベントと紐付けるには以下のandroid:onClick="@{handlers.onChangeClick}"
ようにします。
<Button android:id="@+id/button_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CHANGE"
android:onClick="@{handlers.onChangeClick}"
/>
ボタンのandroid:onClick
はView.OnClickListener
のonClick
に対応した属性で、名前はリスナーのメソッド名に由来するよう定義されています。
別の例としてView.OnLongClickListener
のonLongClick
の場合は、android:onLongClick
となります。(*2)
(*1,*2. ViewBindingAdapter.javaでそのように定義されているため
https://android.googlesource.com/platform/frameworks/data-binding/+/android-7.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewBindingAdapter.java)
イベントハンドラ実装
それでは、Activityにインタフェースを実装して、イベントを受け取ってみましょう。
public class MainActivity extends AppCompatActivity implements SampleEventHandlers {
private User mUser = new User("Taro");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setUser(mUser);
binding.setHandlers(this); //handlersにセット
}
@Override
public void onChangeClick(View view) {
Log.d("DEBUG", "Change User Name");
}
}
これでボタンのクリックイベントを拾ってログが出るようになりました。
Step3. 表示する値を動的に変更する
それでは、ボタンを押したら表示されているユーザー名が変更されるようにしてみましょう。まずは以下を試してみます。
@Override
public void onChangeClick(View view) {
Log.d("DEBUG", "Change User Name");
mUser.setName("Jiro");
Log.d("DEBUG", mUser.getName());
}
これを実行してみると、、、ログには"Jiro"と表示されますが、画面に表示される文字は"Taro"のままです。
プロパティの変更をViewに通知するには、オブジェクトを監視できるよう変更しなければいけません。
BaseObservableを継承する場合
public class User extends BaseObservable{
private String name;
public User(String name){
this.name = name;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
BaseObservableを継承し、getterとsetterに少し変更を加えています。
まず、getName()
に@Bindable
をつけました。これにより、監視用の定数BR.name
が生成されます。
続いて、setName()
にnotifyPropertyChanged(BR.name)
を追加しました。このときにレイアウト側からBR.name
に対応するgetName()
が呼ばれます。
今回はsetterでnotifyPropertyChanged
を呼んでいますが、setter以外でも変更を通知したいタイミングで呼んでOKです。
ObservableFieldを用いる場合
extendsを消費するのが嫌な場合は、別の手段としてフィールドをObservableField
にする方法もありますが、フィールドの型がObservableField
になってしまうので注意です。
public class User{
public ObservableField<String> name = new ObservableField<>();
public User(String name){
this.name.set(name);
}
}
以上のような変更により、ボタンを押すとユーザー名の表示が動的に切り替わるようにできました。
おわりに
Bindingオブジェクトを使う側はオブジェクトをセットするだけで、どう使われるか気にしなくていいところがポイントですね。イベントに関しても、どんなViewから発生したか意識せずに済むようになりました。
参考
https://developer.android.com/topic/libraries/data-binding/index.html?hl=ja
http://qiita.com/kobakei/items/5e902dd24005e6768ac4