Help us understand the problem. What is going on with this article?

Android Databinding 〜超入門〜

More than 3 years have 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

Omoti
Android、ときどきスマートスピーカー
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away