双向数据绑定

使用单向数据绑定,您可以为一个属性设置值,并设置一个侦听器来响应该属性的变化

<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);
        }
    }
}

由于可绑定属性的 getter 方法名为 getRememberMe(),因此该属性对应的 setter 方法会自动使用名称 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

其他资源

要了解有关数据绑定的更多信息,请查阅以下其他资源。

示例

Codelabs

博文