在學(xué)習(xí)dataBinding的數(shù)據(jù)雙向綁定時(shí),針對(duì)自定義特性的雙向數(shù)據(jù)綁定看的一頭霧水,現(xiàn)在有了大致了解,記錄一下
1、XML中使用方式:
單向數(shù)據(jù)綁定
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
雙向數(shù)據(jù)綁定,在單向上加上=
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>
相信大家對(duì)于如何在xml總定義雙向數(shù)據(jù)綁定還是很清楚,但是對(duì)于如何在代碼中使用及操作就有點(diǎn)懵了,下面說(shuō)下流程。
2、代碼中使用方式(分2種情況)
情況一、數(shù)據(jù)綁定是使用在View的系統(tǒng)屬性上,且view中有提供setter方法
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"http://android:checked屬性是CheckBox類中有的屬性,且類中有提供setter方法
/>
情況二、view有相關(guān)屬性,但是類中沒(méi)有提供setter方法,或數(shù)據(jù)綁定是使用在View的自定義屬性上
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:paddingLeft="@={viewmodel.paddingLeft}"http://android:paddingLeft屬性是CheckBox類中有的屬性,但是沒(méi)有提供setter方法
/>
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.myText}"http:// app:time屬性在EditText中不存在
/>
2.1 情況一(數(shù)據(jù)是綁定在系統(tǒng)屬性上的)
? 針對(duì)這種使用的是view存在的屬性且view中有提供setter方法,只需要在數(shù)據(jù)類ViewModel類上做操作就可以了,很簡(jiǎn)單
public class ViewModel extends BaseObservable {
private boolean rememberMe;
private String text;
@Bindable
public Boolean getRememberMe() {
return rememberMe;
}
public void setRememberMe(Boolean value) {
// 避免死循環(huán).
if (rememberMe != value) {
rememberMe = value;
// 通知界面更新.
notifyPropertyChanged(BR.remember_me);
}
}
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
Log.d(TAG, "情況1:源數(shù)據(jù)更改,代碼中調(diào)用這個(gè)setter方法\n情況2:用戶改變界面的值,觸發(fā)反向設(shè)置值步驟3,在onChange中調(diào)用@Bindable修飾過(guò)的屬性的setter方法,設(shè)置最新值");
if (TextUtils.equals(text, this.text)) {
return;
}
this.text = text;
notifyPropertyChanged(BR.text);
}
}
? 首先讓數(shù)據(jù)類繼承
BaseObservable,這樣數(shù)據(jù)類就具有了在數(shù)據(jù)更新時(shí),可以通知界面的方法notifyPropertyChanged(BR.xx);? 針對(duì)要操作的綁定屬性
rememberMe,提供setter和getter方法;? 然后在屬性的getter方法上添加注解
@Bindable,系統(tǒng)會(huì)在BR類中生成一個(gè)條目BR.remember_me,下次如果rememberMe的值更新時(shí),調(diào)用notifyPropertyChanged(BR.remember_me)就可以使界面更新;當(dāng)界面用戶操作導(dǎo)致
CheckBox的check變動(dòng)時(shí),系統(tǒng)會(huì)調(diào)用提供的setter方法。
2.2 情況二(數(shù)據(jù)是綁定在自定義屬性上的)
? 數(shù)據(jù)類ViewModel類的設(shè)置,和情況一的一樣設(shè)置,記得參考上面情況一設(shè)置數(shù)據(jù)類。
? 情況二,還需要針對(duì)xml中自定義的屬性,添加解釋類和方法。
2.2.1 先說(shuō)幾個(gè)個(gè)人理解,方便后續(xù)說(shuō)明
(1)如果我們?cè)趚ml中使用自定義屬性,在當(dāng)前view中不存在setter方法,例如:
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
? 很明顯app:myText=這個(gè)屬性,EditText中是不存在setMyText()方法的,出現(xiàn)這種情況的時(shí)候,編譯器編譯時(shí)就會(huì)報(bào)錯(cuò),解決方法就是,告訴數(shù)據(jù)綁定模塊,如果沒(méi)有setter方法時(shí),應(yīng)該使用我們自己提供的方法。
(2)@BindingAdapter("屬性名"),
當(dāng)給方法添加這個(gè)注解后,表示正向綁定時(shí),設(shè)置的值應(yīng)該通過(guò)這個(gè)方法操作,
// 負(fù)責(zé)解析xml中app:myText的屬性,并調(diào)用下面這個(gè)方法
@BindingAdapter("app:myText")
public static void setText(EditText view, String text) {
if (TextUtils.equals(text,"1")) {
view.setBackground(new ColorDrawable(0xffff0000));
}else {
view.setBackground(new ColorDrawable(0xff0000ff));
}
}
(3)@InverseBindingAdapter("屬性名")
當(dāng)給方法添加這個(gè)注解后,表示反向綁定時(shí),會(huì)調(diào)用這個(gè)方法,
@InverseBindingAdapter(attribute = "app:myText")
public static String getText(EditText view) {
return view.getText().toString();
}
(4)當(dāng)我們?cè)趚ml中使用
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
? 時(shí)
app:myText="@={viewmodel.text}"
系統(tǒng)(編譯器,后續(xù)統(tǒng)稱為系統(tǒng)吧)其實(shí)為我們分解成了下面這兩部分
app:myText="@{viewmodel.text}"
app:myTextAttrChanged="@{inverseBindingListener}"http://inverseBindingListener是系統(tǒng)生成的,我們現(xiàn)在不用管它是哪兒來(lái)的
看到上面這2個(gè)屬性,大家應(yīng)該就明白了,類似我們開(kāi)頭數(shù)據(jù)單向綁定,
app:myText="@{viewmodel.text}"是用來(lái)做數(shù)據(jù)正向綁定的,app:myTextAttrChanged="@{inverseBindingListener}"是用來(lái)做數(shù)據(jù)反向綁定的,一個(gè)回調(diào)對(duì)象
2.2.2 步驟及解釋
? 通過(guò)2.2.1第4點(diǎn)的說(shuō)明,我們看到系統(tǒng)幫我們生成的app:myText和app:myTextAttrChanged在EditText中是不存在的屬性,肯定也沒(méi)有setter方法,所以根據(jù)2.2.1第1點(diǎn),此時(shí)就需要我們自定義方法,來(lái)讓系統(tǒng)使用我們提供的方法
? a.先解決數(shù)據(jù)正向綁定的問(wèn)題,用2.2.1第2點(diǎn)的注解便可:
// 負(fù)責(zé)解析xml中app:myText的屬性,并調(diào)用下面這個(gè)方法
@BindingAdapter("app:myText")
public static void setText(EditText view, String text) {
if (TextUtils.equals(text,"1")) {
view.setBackground(new ColorDrawable(0xffff0000));
}else {
view.setBackground(new ColorDrawable(0xff0000ff));
}
}
// 負(fù)責(zé)解析xml中app:myTextAttrChanged的屬性,并調(diào)用下面這個(gè)方法
@BindingAdapter("app:myTextAttrChanged")
public static void setListener(EditText view, final InverseBindingListener listener) {
//內(nèi)容需要在反向綁定數(shù)據(jù)時(shí),再寫(xiě)。
}
? 通過(guò)上面這兩個(gè)方法,算是完成了數(shù)據(jù)的正向綁定,
? app:myText的方法,其實(shí)就是判斷給定的text是否等于1,然后給EditText設(shè)置不同的背景色;
? app:myTextAttrChanged的方法,就是告訴系統(tǒng)什么時(shí)候,觸發(fā)回調(diào);
? b.數(shù)據(jù)反向綁定:
? 反向綁定其實(shí),就是把用戶在界面的改變,設(shè)置回我們自己的數(shù)據(jù)類上,本例中,就是把用戶輸入的文本,設(shè)置回viewmodel的text屬性上
? 由于我們已經(jīng)給數(shù)據(jù)類ViewModel中的text屬性添加了@Bindable注解,并且上面提到的系統(tǒng)自動(dòng)生成的InverseBindingListener中的onChange()方法是會(huì)幫我們調(diào)用text屬性的setter方法,所以我們只需要告訴數(shù)據(jù)綁定系統(tǒng)在什么時(shí)候設(shè)置和需要設(shè)置什么值便可。
? ① 在什么時(shí)候設(shè)置,由于是當(dāng)用戶輸入時(shí),我們就要同步獲取輸入內(nèi)容,所以我們肯定是要在EditText上添加一個(gè)文本變化監(jiān)聽(tīng)器,用戶只要一輸入,我們通過(guò)監(jiān)聽(tīng)器就能馬上知道,所以我們可以在我們自定義的app:myTextAttrChanged方法中這樣寫(xiě):
// 負(fù)責(zé)解析xml中app:myTextAttrChanged的屬性,并調(diào)用下面這個(gè)方法
@BindingAdapter("app:myTextAttrChanged")
public static void setListener(EditText view, final InverseBindingListener listener) {
if (listener != null) {
view.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Log.d(TAG, "用戶改變界面的值,觸發(fā)反向設(shè)置值步驟1,準(zhǔn)備調(diào)用自動(dòng)生成的onChange");
listener.onChange();
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
}
? 當(dāng)EditText內(nèi)容有變化時(shí),就會(huì)調(diào)用系統(tǒng)生成的InverseBindingListener的onChange()方法,然后系統(tǒng)在onChange()方法中就會(huì)自動(dòng)把值設(shè)置給數(shù)據(jù)類對(duì)象ViewModel,至于值從哪兒來(lái),我們看下面的②;
? ② 需要設(shè)置什么值,其實(shí)在InverseBindingListener的onChange()方法中,系統(tǒng)還會(huì)調(diào)用@InverseBindingAdapter("屬性名")修飾的方法,即2.2.1第3點(diǎn)的方法,所以我們只需要把我們要設(shè)置的值,通過(guò)這個(gè)方法返回便可:
@InverseBindingAdapter(attribute = "app:myText")
public static String getText(EditText view) {
return view.getText().toString();
}
? 至此便完成了,基本的數(shù)據(jù)雙向綁定。
3、流程梳理
前提:
數(shù)據(jù)對(duì)象:
public class MyViewModel extends BaseObservable {
private static final String TAG = "MyObservable";
private String text;
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
if (TextUtils.equals(text, this.text)) {
return;
}
this.text = text;
notifyPropertyChanged(BR.text);
}
}
xml文件:
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
數(shù)據(jù)正向綁定流程:MyObservable.text屬性變化,調(diào)用屬性的setter方法,觸發(fā)notifyPropertyChanged(BR.text),根據(jù)xml中使用,會(huì)調(diào)用@BindingAdapter("app:myText")注解的方法;
數(shù)據(jù)反向綁定流程:EditText內(nèi)容變化時(shí),觸發(fā)InverseBindingListener中的onChange()方法,onChange()中會(huì)調(diào)用獲取值的@InverseBindingAdapter(attribute = "app:myText")注解的方法,然后onChange()中會(huì)調(diào)用MyObservable.text屬性的setter方法,觸發(fā)notifyPropertyChanged(BR.text),根據(jù)xml中使用,會(huì)調(diào)用@BindingAdapter("app:myText")注解的方法;
4、后話
? 其實(shí)數(shù)據(jù)綁定系統(tǒng),也有自己實(shí)現(xiàn)的類,大家可以參考下:androidx.databinding.adapters.ViewBindingAdapter。
? 當(dāng)然還有@BindingMethods和@InverseBindingMethods,以及更高深的用法,我現(xiàn)在還在學(xué)習(xí)中,后續(xù)有時(shí)間會(huì)繼續(xù)更新
? 由于摻雜很多個(gè)人的理解的,文中描述不一定完全正確,歡迎留言指正和討論。