Android DataBinding Tips - Re.Ra.Ku アドベントカレンダー day 10

Re.Ra.Ku アドベントカレンダー 10日目です。

こんにちは、安部です。

今回はAndroidのDataBindingのTipsを少し紹介します。

DataBindingの変数に設定するなど基本的なところは省略しています。

includeしたレイアウトに変数を渡す

includeタグを使ったときに変数を渡す方法です。

includeされるレイアウト

通常のレイアウトと同じようにします。

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="name"
            type="java.lang.String" />
    </data>

    <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="@{name}" />
    </LinearLayout>
</layout>

includeタグ

先程のレイアウトで定義した変数をincludeタグの属性としてapp:nameに渡したい変数を設定することでincludeされた側のほうでも変数が使用可能になります。

<include
    app:name="@{user.name}"
    layout="@layout/custom_view"/>

EditTextの入力を変数に反映する

two-way bindingの実現方法です。

入力を受け取るクラス/変数

EditTextの入力を受けて取るクラスと変数の例です。

public class User {
    public final ObservableField<String> name = new ObservableField<>();
}

レイアウト

分かりにくいのですが、android:textに設定している値の@{の間に=を入れます。こうするとEditTextに入力された値が変数に反映されます。

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={user.name}" />

制限事項としては、android:textにはStringのものしか設定できません。

EditTextの変更を監視する

変更されるたびに呼ばれるメソッド

この場合ですとonTextChangedが変更されるたびに呼ばれます。

public class Handlers {

    private static final String TAG = Handlers.class.getSimpleName();

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.d(TAG, "onTextChanged: " + s);
    }
}

レイアウト

android:onTextChangedに先程つくった処理を設定してあげます。これだけです。

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onTextChanged="@{handlers::onTextChanged}" />

補足

android:onTextChangedがどこで定義されてるかですが、ココ です。

他にもいくつか拡張であるので、ほしいのがないかをココで探すと良いと思います。

よく使いそうな、フォーカスイベントのandroid:onFocusChangeココで定義されてます。

ひとつ問題としては、xmlで属性が定義されてないみたいな警告が出てしまいます。気になる人はコメントで抑制しておくと良いと思います。(tools:ignoreを使った抑制がうまくできなかった…)

<!--suppress AndroidUnknownAttribute -->
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onTextChanged="@{handlers::onTextChanged}" />

注意としてはパッとどこで定義されてるのかが分からないので、何かしらコメントがあると良いかもしれないです。

定義されていないイベント処理する

例えば、SwipeRefreshLayout#setOnRefreshListenerはそのままではレイアウトファイルのみでイベントを設定することが出来ません。

カスタム属性を設定する

次のようなクラスを作って、属性を追加して、それを受け取れるようにします。引数の順番は対象のView、属性に渡す型になります。今回はSwipeRefreshLayout.OnRefreshListenerを受け取るようにします。

public class SwipeRefreshLayoutBinding {

    @BindingAdapter("onRefresh")
    public static void onRefresh(SwipeRefreshLayout view, SwipeRefreshLayout.OnRefreshListener listener) {
        view.setOnRefreshListener(listener);
    }
}

別のパターンとして、@BindingMethodsを使うことも出来ます。こちらはクラスのほうにアノテーションを追加します。

@BindingMethods({
        @BindingMethod(type = SwipeRefreshLayout.class, attribute = "onRefresh", method = "setOnRefreshListener")
})
public class SwipeRefreshLayoutBinding {

}

イベントを処理するメソッドを定義する

SwipeRefreshLayout.OnRefreshListenerで定義されているメソッドと同じシグネチャのメソッドを定義します。メソッド名は変更しても大丈夫です。

public class Handlers {

    private static final String TAG = Handlers.class.getSimpleName();

    public void onRefresh() {
        Log.d(TAG, "onRefresh");
    }
}

レイアウトに設定する

先程の作った属性(名前空間はappを使います)とメソッドを設定してあげると、コードを書かずにイベントを処理できるようになります。

<android.support.v4.widget.SwipeRefreshLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:onRefresh="@{handlers::onRefresh}">

    ...

</android.support.v4.widget.SwipeRefreshLayout>

まとめ

頑張ればDataBindingで色々できそうですが、あまりトリッキーなことをしすぎない感じのほうが良いとは思います。そこはうまくバランスを取りながらで。

うまくDataBindingを使って、コードを簡潔にしていきたいですね。