之前介紹了如何使用 DataBinding 骚灸,其中涉及到ViewModel
的定義甚牲,這期我們就詳細解析一下如何定義及使用ViewModel
Observable Objects
在 DataBinding 中有個接口Observable
蝶柿,實現(xiàn)這個接口可以添加一個監(jiān)聽器來綁定觀察類中的屬性是否有變化。有個實現(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);
}
}
}
可以看到這個類幫我們實現(xiàn)了Observable
接口里的方法戒洼,并且添加了notifyChange()
和notifyPropertyChanged(int fieldId)
方法供子類調(diào)用圈浇。這里會讓人聯(lián)想到JDK中的java.util.Observable
靴寂,它是個類不是接口召耘,也沒有實現(xiàn)任何接口怎茫,這個設(shè)計有點糟糕,因為Java是不支持多繼承的蜜宪,如果子類需要java.util.Observable
和另一個超類的行為祥山,就會陷入兩難境界。DataBinding 的設(shè)計有效的避免了這種問題澳窑,它提供一個接口定義了規(guī)范供常,并提供一個實現(xiàn)了接口的超類供繼承栈暇,如果覺得BaseObservable
占了繼承位也可以自己定義超類實現(xiàn)Observable
接口。
?現(xiàn)在我們重新觀察BaseObservable
這個類煎源,notifyPropertyChanged(int fieldId)
是一個非常有用的方法香缺,在子類的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
兽埃,這樣會在編譯時在BR
中生成一個標識倔撞,這樣能夠鑒定這個屬性是否被修改過痪蝇。BR
是編譯時生成的類似于Android中的R.class
的文件,其中也是標識了所有你在DataBinding 中定義的類和屬性趁矾。而在自動生成的MainBinding.class
中也印證了這一點给僵,當調(diào)用mainBinding.setUser(user)
時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
如果覺得只需要部分數(shù)據(jù)需要進行數(shù)據(jù)綁定帝际,無需繼承這么麻煩,可以使用ObservableField
包括 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable, ObservableArrayMap, ObservableArrayList
具體可見
綁定事件
先定義一個ActionHandler
類斑粱,定義兩個方法脯爪,用來體現(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 會默認傳入當前View
的實例進入方法體掖举,所以直接寫actionHandler.showPhone
就可以。另一種用法是使用λ表達式拇泛。
<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
}
第二種方式可以理解為此處使用了一個OnClickListener
匿名內(nèi)部類的方式調(diào)用actionHandler.showPhone(user)蜈垮,通常情況推薦第二種攒发,因為點擊事件可能會引起數(shù)據(jù)的改變晋南,第二種方式直接傳入ViewModel
,更方便操作數(shù)據(jù)偶妖。
@BindingAdapter 自定義屬性值
假如需要為EidtText
設(shè)置TextWatcher
,在xml
中是不支持該屬性的态秧,這時我們可以自定義屬性扼鞋,使用@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 默認第一個參數(shù)必須是View
,而且可以缺省傳入竿痰,所以xml
中可以寫為
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:afterTextChanged="@{user}"/>
@BindingAdapter({"android:afterTextChanged"})
中定義了命名空間和屬性名影涉,關(guān)于命名空間可以參考stackoverflow這個問題ianhanniballake的回答。
對于相對復雜的交互匣缘,可能需要多個屬性值協(xié)作鲜棠,@BindingAdapter
也支持傳入多個屬性,需要在定義時傳入數(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
是默認傳入表鳍,此處每個屬性傳入一個參數(shù)即可祥诽,如果某個屬性未定義,則由默認值決定厘熟,如android:clickable="@{true}"
不聲明,并不影響Button
是否可以點擊颇玷。關(guān)于@BindingAdapter的工作原理可以參考這里
之后會嘗試寫一個基于DataBinding 的小項目就缆,如果遇到什么坑會及時分享出來竭宰。