Двусторонняя привязка данных

При односторонней привязке данных вы можете задать значение атрибута и настроить прослушиватель, который будет реагировать на изменение этого атрибута:

<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.

Например, чтобы включить двустороннюю привязку данных для атрибута "time" в специальном представлении MyView, выполните следующие действия:

  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

Дополнительные ресурсы

Чтобы узнать больше о привязке данных, ознакомьтесь со следующими дополнительными ресурсами.

Образцы

Практические работы

Записи в блоге