Android中的MVVM DataBinding指南(二)

之前介紹了如何使用 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í)分享出來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容