之前介紹了如何使用 DataBinding ,其中涉及到ViewModel的定義,這期我們就詳細(xì)解析一下如何定義及使用ViewModel
Observable Objects
在 DataBinding 中有個(gè)接口Observable,實(shí)現(xiàn)這個(gè)接口可以添加一個(gè)監(jiān)聽器來綁定觀察類中的屬性是否有變化。有個(gè)實(shí)現(xiàn)了Observable的父類BaseObservable,由 DataBinding 提供,代碼如下
public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
public BaseObservable() {
}
@Override
public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
mCallbacks.add(callback);
}
@Override
public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
if (mCallbacks != null) {
mCallbacks.remove(callback);
}
}
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
public void notifyPropertyChanged(int fieldId) {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, fieldId, null);
}
}
}
可以看到這個(gè)類幫我們實(shí)現(xiàn)了Observable接口里的方法,并且添加了notifyChange()和notifyPropertyChanged(int fieldId)方法供子類調(diào)用。這里會讓人聯(lián)想到JDK中的java.util.Observable,它是個(gè)類不是接口,也沒有實(shí)現(xiàn)任何接口,這個(gè)設(shè)計(jì)有點(diǎn)糟糕,因?yàn)?em>Java是不支持多繼承的,如果子類需要java.util.Observable和另一個(gè)超類的行為,就會陷入兩難境界。DataBinding 的設(shè)計(jì)有效的避免了這種問題,它提供一個(gè)接口定義了規(guī)范,并提供一個(gè)實(shí)現(xiàn)了接口的超類供繼承,如果覺得BaseObservable占了繼承位也可以自己定義超類實(shí)現(xiàn)Observable接口。
?現(xiàn)在我們重新觀察BaseObservable這個(gè)類,notifyPropertyChanged(int fieldId)是一個(gè)非常有用的方法,在子類的setter中調(diào)用,能夠更新View中的數(shù)據(jù)。例如
public class MainViewModel extends BaseObservable {
private String phone;
public MainViewModel(String phone) {
this.phone = phone;
}
public void setPhone(String phone) {
this.phone = phone;
notifyPropertyChanged(BR.phone);
}
@Bindable
public String getPhone() {
return phone;
}
上面的MainViewModel中的getter加入注解@Bindable,這樣會在編譯時(shí)在BR中生成一個(gè)標(biāo)識,這樣能夠鑒定這個(gè)屬性是否被修改過。BR是編譯時(shí)生成的類似于Android中的R.class的文件,其中也是標(biāo)識了所有你在DataBinding 中定義的類和屬性。而在自動生成的MainBinding.class中也印證了這一點(diǎn),當(dāng)調(diào)用mainBinding.setUser(user)時(shí)MainBinding也有調(diào)用notifyPropertyChanged(BR.user)
public class MainBinding extends android.databinding.ViewDataBinding {
public void setUser(com.winter.huang.databinding.viewmodel.MainViewModel user) {
updateRegistration(0, user);
this.mUser = user;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
}
顆粒度更小的ObservableField
如果覺得只需要部分?jǐn)?shù)據(jù)需要進(jìn)行數(shù)據(jù)綁定,無需繼承這么麻煩,可以使用ObservableField 包括 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable, ObservableArrayMap, ObservableArrayList具體可見
綁定事件
先定義一個(gè)ActionHandler類,定義兩個(gè)方法,用來體現(xiàn)綁定事件的兩種方式
public class ActionHandler {
public void showPhone(MainViewModel viewModel) {
Toast.makeText(AppApplication.getAppContent(), viewModel.getPhone(), Toast.LENGTH_SHORT).show();
}
public void showPhone(View view) {
}
}
可以在xml中定義并使用
<import type="com.winter.huang.databinding.utils.ActionHandler" alias="ActionHandler"/>
<variable
name="actionHandler"
type="ActionHandler"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="@+id/button"
android:onClick="@{actionHandler.showPhone}"/>
上面這種方法會調(diào)用showPhone(View view),DataBinding 會默認(rèn)傳入當(dāng)前View的實(shí)例進(jìn)入方法體,所以直接寫actionHandler.showPhone就可以。另一種用法是使用λ表達(dá)式。
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="@+id/button"
android:onClick="@{() -> actionHandler.showPhone(user)}"/>
兩種方式是有本質(zhì)區(qū)別的,第一種方式和以往在xml中聲明android:onClick=showPhone,之后在Activity中定義是相同的。
public void showPhone(View view) {
//do something
}
第二種方式可以理解為此處使用了一個(gè)OnClickListener匿名內(nèi)部類的方式調(diào)用actionHandler.showPhone(user),通常情況推薦第二種,因?yàn)辄c(diǎn)擊事件可能會引起數(shù)據(jù)的改變,第二種方式直接傳入ViewModel,更方便操作數(shù)據(jù)。
@BindingAdapter 自定義屬性值
假如需要為EidtText設(shè)置TextWatcher,在xml中是不支持該屬性的,這時(shí)我們可以自定義屬性,使用@BindingAdapter注解,定義公有靜態(tài)方法傳入需要操作的View和其他參數(shù)即可,下面的代碼為EditText設(shè)置屬性android:afterTextChanged
@BindingAdapter({"android:afterTextChanged"})
public static void showViewModel(final EditText editText, final MainViewModel viewModel) {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (!s.toString().equals(viewModel.getPhone())) {
viewModel.setPhone(s.toString());
}
}
});
}
由于DataBinding 默認(rèn)第一個(gè)參數(shù)必須是View,而且可以缺省傳入,所以xml中可以寫為
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:afterTextChanged="@{user}"/>
@BindingAdapter({"android:afterTextChanged"}) 中定義了命名空間和屬性名,關(guān)于命名空間可以參考stackoverflow這個(gè)問題ianhanniballake的回答。
對于相對復(fù)雜的交互,可能需要多個(gè)屬性值協(xié)作,@BindingAdapter也支持傳入多個(gè)屬性,需要在定義時(shí)傳入數(shù)組即可
public class ActionHandler implements View.OnClickListener{
@BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener onClickListener, boolean clickable) {
view.setOnClickListener(onClickListener);
view.setClickable(clickable);
}
@Override
public void onClick(View v) {
Toast.makeText(AppApplication.getAppContent(), "onClick", Toast.LENGTH_SHORT).show();
}
}
在xml中
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="@{true}"
android:onClick="@{actionHandler}" />
屬性的順序可以不依賴定義的順序,同樣View是默認(rèn)傳入,此處每個(gè)屬性傳入一個(gè)參數(shù)即可,如果某個(gè)屬性未定義,則由默認(rèn)值決定,如android:clickable="@{true}"不聲明,并不影響Button是否可以點(diǎn)擊。關(guān)于@BindingAdapter的工作原理可以參考這里
之后會嘗試寫一個(gè)基于DataBinding 的小項(xiàng)目,如果遇到什么坑會及時(shí)分享出來。