双方向データ バインディング

一方向データ バインディングを使用すると、属性の値を設定し、その属性の変更に反応するリスナーを設定できます。

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>

双方向データ バインディングを使用すると、このプロセスをショートカットできます。

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"
/>

@={} 表記(重要な意味として「=」記号を含む)は、プロパティへのデータ変更を受け取り、同時にユーザーの更新をリッスンします。

バッキング データの変更に対応するには、次のコード スニペットに示すように、レイアウト変数を Observable(通常は BaseObservable)の実装にし、@Bindable アノテーションを使用します。

Kotlin

class LoginViewModel : BaseObservable {
    // val data = ...

    @Bindable
    fun getRememberMe(): Boolean {
        return data.rememberMe
    }

    fun setRememberMe(value: Boolean) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value

            // React to the change.
            saveData()

            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me)
        }
    }
}

Java

public class LoginViewModel extends BaseObservable {
    // private Model data = ...

    @Bindable
    public Boolean getRememberMe() {
        return data.rememberMe;
    }

    public void setRememberMe(Boolean value) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value;

            // React to the change.
            saveData();

            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me);
        }
    }
}

バインド可能なプロパティのゲッター メソッドは getRememberMe() と呼ばれるため、プロパティの対応するセッター メソッドで自動的に setRememberMe() という名前が使用されます。

BaseObservable@Bindable の使用方法については、オブザーバブルなデータ オブジェクトを操作するをご覧ください。

カスタム属性を使用した双方向データ バインディング

プラットフォームには、アプリの一部として使用できる最も一般的な双方向属性と変更リスナーの双方向データ バインディングの実装が用意されています。カスタム属性を持つ双方向データ バインディングを使用する場合は、@InverseBindingAdapter アノテーションと @InverseBindingMethod アノテーションを操作する必要があります。

たとえば、MyView というカスタムビューの "time" 属性で双方向データ バインディングを有効にする場合は、次の手順を完了します。

  1. @BindingAdapter を使用して、初期値を設定し、値が変更されたときに更新するメソッドにアノテーションを付けます。

    Kotlin

    @BindingAdapter("time")
    @JvmStatic fun setTime(view: MyView, newValue: Time) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue
        }
    }

    Java

    @BindingAdapter("time")
    public static void setTime(MyView view, Time newValue) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue;
        }
    }
  2. ビューから値を読み取るメソッドに、@InverseBindingAdapter を使用してアノテーションを付けます。

    Kotlin

    @InverseBindingAdapter("time")
    @JvmStatic fun getTime(view: MyView) : Time {
        return view.getTime()
    }

    Java

    @InverseBindingAdapter("time")
    public static Time getTime(MyView view) {
        return view.getTime();
    }

この時点で、データ バインディングは、データが変更されたときに何を行うか(@BindingAdapter アノテーションを付けたメソッドを呼び出す)と、ビュー属性が変更されたときに何を呼び出すか(InverseBindingListener を呼び出す)を認識しています。ただし、属性がいつ、どのように変更されるかは不明です。

こうしたことから、リスナーはビューに設定する必要があります。これは、カスタムビューに関連付けられたカスタム リスナーの場合もあれば、フォーカスの喪失やテキストの変更などの一般的なイベントである場合もあります。プロパティの変更に対するリスナーを設定するメソッドに @BindingAdapter アノテーションを追加します。

Kotlin

@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
        view: MyView,
        attrChange: InverseBindingListener
) {
    // Set a listener for click, focus, touch, etc.
}

Java

@BindingAdapter("app:timeAttrChanged")
public static void setListeners(
        MyView view, final InverseBindingListener attrChange) {
    // Set a listener for click, focus, touch, etc.
}

リスナーには InverseBindingListener がパラメータとして含まれています。InverseBindingListener を使用して、属性が変更されたことをデータ バインディング システムに通知します。これにより、システムは @InverseBindingAdapter アノテーションを付けたメソッドの呼び出しを開始できるようになります。

実際には、このリスナーには一方向データ バインディングのリスナーなど、重要なロジックが含まれています。例については、テキスト属性の変更用のアダプター TextViewBindingAdapter をご覧ください。

コンバージョンに至ったユーザー

View オブジェクトにバインドされている変数を表示する前に、なんらかの方法でフォーマット、変換、変更が必要な場合は、Converter オブジェクトを使用できます。

例として、日付を表す EditText オブジェクトを見てみましょう。

<EditText
    android:id="@+id/birth_date"
    android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>

viewmodel.birthDate 属性には Long 型の値が含まれているため、コンバータを使用してフォーマットする必要があります。

双方向の式が使用されているため、ユーザーが指定した文字列をバッキング データ型(この場合は Long)に戻す方法をライブラリに伝えるための逆コンバータも必要です。このプロセスを実行するには、@InverseMethod アノテーションをいずれかのコンバータに追加し、このアノテーションで逆コンバータを参照するようにします。この構成の例を次のコード スニペットに示します。

Kotlin

object Converter {
    @InverseMethod("stringToDate")
    @JvmStatic fun dateToString(
        view: EditText, oldValue: Long,
        value: Long
    ): String {
        // Converts long to String.
    }

    @JvmStatic fun stringToDate(
        view: EditText, oldValue: String,
        value: String
    ): Long {
        // Converts String to long.
    }
}

Java

public class Converter {
    @InverseMethod("stringToDate")
    public static String dateToString(EditText view, long oldValue,
            long value) {
        // Converts long to String.
    }

    public static long stringToDate(EditText view, String oldValue,
            String value) {
        // Converts String to long.
    }
}

双方向データ バインディングを使用した無限ループ

双方向データ バインディングを使用する際には、無限ループが発生しないように注意してください。ユーザーが属性を変更すると、@InverseBindingAdapter アノテーションを付けたメソッドが呼び出され、値がバッキング プロパティに割り当てられます。これにより、@BindingAdapter アノテーションを付けたメソッドが呼び出され、@InverseBindingAdapter アノテーションを付けたメソッドへの別の呼び出しがトリガーされます。

このため、@BindingAdapter アノテーションを付けたメソッドで新しい値と古い値を比較して、無限ループを回避することが重要です。

双方向属性

次の表に示す属性を使用する場合、プラットフォームには双方向データ バインディングの組み込みサポートが用意されています。プラットフォームがこのサポートを提供する方法の詳細については、対応するバインディング アダプターの実装をご覧ください。

クラス 属性 バインディング アダプター
AdapterView android:selectedItemPosition
android:selection
AdapterViewBindingAdapter
CalendarView android:date CalendarViewBindingAdapter
CompoundButton android:checked CompoundButtonBindingAdapter
DatePicker android:year
android:month
android:day
DatePickerBindingAdapter
NumberPicker android:value NumberPickerBindingAdapter
RadioButton android:checkedButton RadioGroupBindingAdapter
RatingBar android:rating RatingBarBindingAdapter
SeekBar android:progress SeekBarBindingAdapter
TabHost android:currentTab TabHostBindingAdapter
TextView android:text TextViewBindingAdapter
TimePicker android:hour
android:minute
TimePickerBindingAdapter

参考情報

データ バインディングの詳細については、次の参考情報をご覧ください。

サンプル

Codelab

ブログ投稿