DataBindingを試す

Last Update: 2016-02-15

DataBindingがアツいらしいと聞いて試してみました。簡単な使い方をするなら想像以上に簡単でした。

今までActivityなどでfindViewByIdを書きたくないから、ButterKnifeをどのプロジェクトでも使っていたのですが、DataBindingを使えば同じようなことができます。

両者を使ってみて感じたのは、ButterKnifeがレイアウトXMLをJavaコードに持ってくるイメージであるとすれば、DataBindingはJavaコードをレイアウトXMLに持っていくイメージであるということです。

DataBindingを使うことで、Javaで作成したコードを、レイアウトXMLに埋め込むことができるようになります。レイアウトXMLでどのデータを使うか指定しておけば、Activityで「このクラス(のインスタンス)を使ってくれ」と指定するだけでその内容を表示できたりします。

具体的な使い方はData Binding Guide – Android Developersを参照してください。

DataBindingを使う設定

Android Studio 1.3以上であることが必須です。

Android Gradle Plugin 1.5.0-alpha1以上を使っていることが必須、でした。

Android Studio 2.0 betaになると、コード補完のサポートがより強力になってます。

build.gradleでDataBindingの設定を有効にすることで利用できます。

android {
    ....
    dataBinding {
        enabled = true
    }
}

表示するデータを保持するクラスの作成

public class Character{
    public String name;
    public int age;
    public String skill;

    public Character(String name, int age, String skill){
        this.name = name;
        this.age = age;
        this.skill = skill;
    }

}

DataBindingを使ってアクセスするには、publicなフィールドであるか、privateなフィールドである場合publicなgetterがあることが必須です。

レイアウトXML

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <data>
        <variable
            name="chara"
            type="jp.gcreate.sample.databinding.Character"
            />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="jp.gcreate.sample.databinding.MainActivity"
        >

        <TextView
            android:id="@+id/chara_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{chara.name}"
            />

        <TextView
            android:id="@+id/chara_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{Integer.toString(chara.age)}"
            />

        <TextView
            android:id="@+id/chara_skill"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{chara.skill}"
            />

    </LinearLayout>

</layout>

ポイントはこんな感じ。

  • レイアウトXMLを``タグで囲む
  • レイアウトXML内で利用するクラスを``タグ内で宣言する
  • ``タグ内で宣言するクラス名は完全修飾ドメイン名
  • ``タグ内で宣言した`name`を使ってアクセスする
  • Javaのコードを埋め込める(`Integer.toString()`とか)
  • コードは`@{}`で囲む

ちなみにandroid:textのところに埋め込むデータは、TextView.setText()を使って設定されるので、ここにint型のデータを埋め込むときは注意が必要です。何も考えずにint型のデータを表示しようとすると、そのint値がリソースIDとして認識されてしまいエラーになるからです。その際のエラー内容も、「そんなリソースIDないぞ」という内容で軽くハマりました。

Activityの処理

ちなみにDataBindingによって生成されるクラス名は、レイアウトXMLのファイル名をスネークケースからキャメルケースに変換したものに、Bindingを付け加えたものになります。

activity_main.xmlならActivityMainBindingに、hoge_hoge.xmlならHogeHogeBindingになります。

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    Character chara;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        chara = new Character("桃太郎", 18, "きびだんご");
        binding.setChara(chara);
    }
}

レイアウトXMLに紐付けるクラスを、setChara()で渡すことで、渡したCharacterクラスのデータが表示されます。ちなみにレイアウトXMLで<variables name="chara" .../>と設定しているからsetChara()になっています。nameをhogeにしていたらsetHoge()になります。

データの変更を反映させたい

単に表示するだけでは「何が便利なのか」という感じるかもしれません。

例えば桃太郎のスキルを「鬼退治」に変更したいとします。findViewByIdを使わずとも、DataBindingを使えばレイアウトXMLでidを割り振ったViewにアクセスすることができます。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        binding.charaSkill.setText("鬼退治");
        return super.onTouchEvent(event);
    }

これを追加すると、タッチイベントが発生したらスキルの内容が「鬼退治」に変わります。

インスタンスの内容が変わったら自動的に反映されるようにする

レイアウトXMLと紐付けるクラスを以下のように書き換えると、インスタンスの内容が変更されるだけでUIに表示されるデータも変わります。

public class Character extends BaseObservable {
    public String name;
    @Bindable
    public int age;
    public String skill;

    public Character(String name, int age, String skill) {
        this.name = name;
        this.age = age;
        this.skill = skill;
    }

    public void countUp(){
        age++;
        notifyPropertyChanged(jp.gcreate.sample.databinding.BR.age);
    }
}
  • BaseObservableのサブクラスにする
  • 自動的に変更させたいpublicなフィールド、もしくはgetterに@Bindableアノテーションを付ける
  • データの変更が生じるメソッドで`notifyPropertyChanged()`を呼ぶ

Activityのコードを以下のように書き換えてみます。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        chara.countUp();
        return super.onTouchEvent(event);
    }

こうするとタッチイベントが生じる度に桃太郎が年をとるようになります。いちいち自分でTextViewに変更済みのデータを設定する必要がなくなります。

今のところ私はこの程度しか使っていないのですが、これだけでも充分便利だなぁと思います。とりあえずは簡単なところから試してみてはいかがでしょうか。