Android
DataBinding

Android Databinding 〜超入門〜

More than 1 year has passed since last update.


はじめに

 いまさらですが、DataBindingを使いだしたので、数回に分けてまとめていこうと思います。

 findViewByIdとかButterKnifeの代替としてよりも、Viewとロジックを分離するような用途を想定しています。

 今回は、DataBindingの導入からオブジェクトのプロパティの変更を監視してViewに反映するまでです。


Step0. 導入

appのgradleに以下を追加


build.gradle

android{

...
dataBinding {
enabled = true
}
}

以上


Step1. まずはオブジェクトの値を表示してみる


バインドするクラスを定義

例として、以下のようなUserクラスを用意しました。


User.java

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は発生しません。


activity_main.xml

<?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の場合


MainActivity.java

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の場合


SampleFragment.java

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オブジェクトを指定しましたが、イベントハンドラを登録することもできます。ここではユーザー名を変更するイベントを持つインタフェースを用意して設定してみます。


SampleEventHandlers.java

public interface SampleEventHandlers {

void onChangeClick(View view);
}

onChangeClickの引数は、クリックイベントと対応づけるためにView.OnClickListeneronClickと同じ(View view)にしてください。(*1)

 これをUserと同様に<variable>に記述することで、各Viewから利用できるようになります。


activity_main.xml

<data>

<variable name="user" type="com.example.databinding.User" />
<variable name="handlers" type="com.example.databinding.SampleEventHandlers" />
</data>


クリックイベントと紐付け

 ButtonのonClickイベントと紐付けるには以下のandroid:onClick="@{handlers.onChangeClick}"ようにします。


activity_main.xml

<Button android:id="@+id/button_change"

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CHANGE"
android:onClick="@{handlers.onChangeClick}"
/>

 ボタンのandroid:onClickView.OnClickListeneronClickに対応した属性で、名前はリスナーのメソッド名に由来するよう定義されています。

 別の例としてView.OnLongClickListeneronLongClickの場合は、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にインタフェースを実装して、イベントを受け取ってみましょう。


MainActivity.java

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. 表示する値を動的に変更する

 それでは、ボタンを押したら表示されているユーザー名が変更されるようにしてみましょう。まずは以下を試してみます。


MainActivity.java

@Override

public void onChangeClick(View view) {
Log.d("DEBUG", "Change User Name");

mUser.setName("Jiro");

Log.d("DEBUG", mUser.getName());
}


 これを実行してみると、、、ログには"Jiro"と表示されますが、画面に表示される文字は"Taro"のままです。

 プロパティの変更をViewに通知するには、オブジェクトを監視できるよう変更しなければいけません。


BaseObservableを継承する場合


User.java

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になってしまうので注意です。


User.java

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