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

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

    <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)をご覧ください。

Converter

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

参考情報

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

サンプル

コードラボ

ブログ投稿