前言

使用單向數據綁定可以在屬性上設置數值,並可設定對該屬性的變化作出反應的監聽器。如下

<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 註釋,如下

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

由於 bindable 屬性的 getter 方法稱為 getRememberMe(),因此屬性的相應 setter 方法會自動使用名稱 setRememberMe()。
關於使用 BaseObservable 和 @Bindable 的更多訊息,參考 Work with observable data objects.

Two-way data binding using custom attributes

該平台為最常見的雙向屬性和監聽器提供雙向數據綁定的實現,使用者可以將當作應用程序的一部分。
若使用者想在自定義屬性上使用雙向資料綁定則必須了解如何使用 @InverseBindingAdapter 和 @InverseBindingMethod 註解。
例如,如果要在名稱為 MyView 的自定義視圖中對 “time” 屬性啟用雙向數據綁定,必須完成以下步驟
1.註釋設置初始值的方法,並在使用 @BindingAdapter 更改時進行更新

@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 註釋從視圖中讀取數值的方法

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

此時,data binding library 知道數據變化時要執行的操作(呼叫使用 @BindingAdapter 註釋的方法)以及視圖屬性變化時調用的內容(它呼叫 InverseBindingListener)。
為此,需要在視圖上設置一個監聽器。它可以是與自定義視圖關聯的自定義監聽器,也可以是通用事件,例如失去焦點或文本更改。
將 @BindingAdapter 註釋添加到為該屬性的變化設置監聽器的方法,如下

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

這個 Listener 包含了 InverseBindingListener 作為參數,可以使用 InverseBindingListener 告知 data binding library 該屬性已更改。接著,系統可以開始使用 @InverseBindingAdapter 呼叫註釋方法,依此類推。
注意每個雙向綁定都會生成一個合成事件屬性( synthetic event attribute)。此屬性與基本屬性具有相同的名稱,但具有後綴 “AttrChanged”。
合成事件屬性允許 data binding library 建立使用 @BindingAdapter 註釋的方法,以將事件監聽器連結到 View 的相應實體。
在實作中,該監聽器包括一些特殊的邏輯,包括用於單向數據綁定的監聽器。相關範例,參考 TextViewBindingAdapter
 

Converters

如果綁定到 View 對象的變數需要在顯示之前進行格式化,轉換或更改,則可以使用 Converter 對象。
例如,使用顯示日期的 EditText 對象

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

viewmodel.birthDate 屬性包含 Long 類型的值,因此需要使用轉換器對其進行格式化。
因為使用雙向表達式,所以還需要一個反向轉換器(inverse converter)讓 data binding library 知道如何將字符串轉換回數據類型,在本例中為 Long。
此過程通過將 @InverseMethod 註釋添加到其中一個轉換器並使此註釋引用反向轉換器來完成。如下

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

 

Infinite loops using two-way data binding

使用雙向數據綁定時,小心不要引入無限迴圈。
當用戶更改屬性時,將呼叫使用 @InverseBindingAdapter 註釋的方法,並將數值分配給 屬性。反過來,這將呼叫使用 @BindingAdapter 註釋的方法,這將觸發對使用 @InverseBindingAdapter 註釋的方法的另一個調用,依此類推。
因此,通過比較使用 @BindingAdapter 註釋的方法中的新値和舊值來打破可能的無限循環非常重要。
 

Two-way attributes

下表為平台預設的雙向綁定屬性表。

Class Attribute(s) Binding adapter
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