DataBinding: 是 google 爲開發者提供的實用工具套庫 Jetpack 中的一個組件庫,他是基於APT(Annotation Processing Tool) 實現的一個MVVM框架庫。使用 DataBinding 咱們能夠很輕鬆的完成M-V 的雙向綁定。html
// 例如這裏與下面的XML就完成了一個雙向綁定的過程 public class UserViewModel extends BaseObservable { private String name = "summer"; public void setName(String name) { this.name = name } @Bindable public String getName() { return name; } }
<!-- 對應的XML佈局 layout_user.xml --> <layout> <data> <variable name="userViewModel" type="com.jvup.model.UserViewModel"/> </data> <FrameLayout> <EditText android:layout_width="300dp" android:layout_height="39dp" android:text="@={userViewModel.name}"> </FrameLayout> </layout>
DataBinding 是一個基於 APT 技術的MVVM框架,DataBinding 使用了觀察這模式來完成雙向通知,經過 APT 技術在編譯時掃描 XML 佈局中的標記生成一個繼承自 ViewDataBinding 的子類並寫入相關綁定代碼,保存在build目錄下,不一樣版本的 android studio 具體路徑不一,能夠經過切換成android工程目錄模式後在java(generated)目錄下查找,保存類名爲佈局名+BindingImpl(如上面的例子layout_user.xml 對應 LayoutUserBindingImpl)。並創建相關綁定事件ID(在BR資源中存放),經過ID能夠主動發起相關更新事件通知。(關於詳細實現另作源碼分析)。ViewDataBinding 內部維護了觀察者註冊與通知而且持有 全部 View 對象以及綁定註冊的model對象。
java
android studio 老版本android
//build.gradle(:app) 那個Module使用就在那個module裏寫 android { dataBinding { enabled = true } }
android studio 4.1以後版本app
//build.gradle(:app) 那個Module使用就在那個module裏寫 android { buildFeatures { dataBinding = true } }
xml佈局:框架
- 必需使用
<layout>
標記做爲起始根標記,解析掃描 xml 時經過識別這個標記來生成對應 ViewDataBinding 類。- 使用
<data>
標記來引入外部綁定數據及類型。<variable>
,<import>
是<data>
子標籤.
<variable>
用來引入綁定中使用的變量,如:須要用來作雙向綁定的 ViewModel;
<import>
用來引入綁定中使用的類型,如:靜態工具類。佈局中想使用某個類型必須經過該標記來申明引入,最終引入的類型會被寫入到生成的 ViewDataBinding 類引入包中。經過這種方式在生成代碼時明確引用類。- 綁定標籤屬性的 value 必須使用 @{} 包裹,掃描經過識別 @{} 來肯定那些屬性須要創建綁定關係。@{}只能創建單向綁定,即 Model 數據到 View 的賦值,若是想接受 View 屬性值改變,則可使用@={} 來完成雙向綁定;
- 屬性匹配規則默認使用 javaBean 約定,經過 getter/setter 方法來匹配,若是 View 提供了 setter 方法(android:adapter 就取 adapter 檢查是否存在 setAdapter 方法),匹配屬性綁定的賦值方式就會調用這個方法。例如: ListView 提供了 setAdapter(ListAdapter adapter) 方法,那麼咱們就能夠直接在佈局中寫
android:adapter="@{userViewModel.adapter}"
,匹配成功後會在生成的 ViewDataBinding 類中生成添加listView.setAdapter(userViewModel.adapter);
的相關代碼來完成綁定;儘管 android:adapter 並非基礎標準的 android 屬性標籤,咱們也並未對該屬性標籤作過自定義標籤描述。僅僅當 android:adapter 的值爲@{}時,這個屬性標籤被看成 dataBinding 綁定屬性時能夠這麼作。或者說 databinding 並不受標準的xml解析約束。自定義View也同樣,所以能夠經過提供 setter 方法來減小自定義屬性的配置(attrs.xml)- 儘管有上面的默認匹配規則,可是任然仍是會有不少屬性並不是遵循了 javaBean 約定,例如 ImageView 能夠經過 android:src="@{userViewModel.head}" 來綁定圖片源,可是 ImageView 並無提供 setSrc() 方法,而設置圖片的方法是 setImageDrawable()、setImageURI() 等這些方法;但咱們卻也能夠經過綁定,是由於google爲咱們提供了許多擴展標記行爲的註解,幫助咱們完成特殊需求下的匹配綁定,例如 @BindingAdapter 能夠擴展綁定行爲,固然還有其餘更豐富的的註解能夠組合完成任何雙向綁定的需求和複雜的中間過程。我在另一篇文章裏有描述用法。 Google 還爲咱們提供了一些針對基礎 View 默認的相關注解實現庫,能夠在 androidx.databinding.adapters 包中找到,固然能夠參考學習。
以上最後兩條匹配規則都須要注意賦值必定須要與匹配方法的參數類型一致
<layout> <data> <variable name="userViewModel" type="com.jvup.model.UserViewModel" /> <import type="android.view.View" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:visibility="@{userViewModel.name == null ? View.GONE : View.VISIBLE}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{userViewModel.name}" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={userViewModel.sex}" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@{userViewModel.head}" /> <ListView android:layout_width="match_parent" android:layout_height="wrap_content" android:adapter="@{userViewModel.adapter}" /> </FrameLayout> </layout>
ViewModel規則:工具
- 屬性匹配規則默認遵循 javaBean 約定,提供 getter 方法完成 ViewModel 屬性賦值到控件,例如:
userViewModel.getHead()
- 若是須要雙向綁定,需提供 setter 方法接受控件屬性值更新到 viewmodel 的屬性值。完成雙向綁定的過程當中, getter 方法的返回值類型必定要與 setter 方法的參數值同樣,不然會報錯。例如:
userViewModel.setSex(String sex) 和 userViewModel.getSex()
- 若是遇到控件值與設置的屬性值類型不一致的狀況,想完成雙向綁定能夠藉助 DataBinding 框架提供的相關注解來完成。如 @BindingConversion , 相關資料可看註解篇
- 固然直接使用屬性也是能夠的,直接使用僅只在第一次綁定時賦值,如:
userViewModel.adapter
,以後只能經過 notifyChange() 來完成更新,這個更新會刷新所有綁定屬性。- 若是但願能夠在 ViewModel 值改變時能夠通知 View 控件也改變,則須要標記註解 @Bindable 。在APT掃描階段會爲 @Bindable 描述過的屬性方法生成一個 BR.ID 用於注入觀察者時使用,這樣咱們就能夠經過這個 BR.ID 來刷新同步數據。這個 BR.ID 的生成規則,就是取變量名或者 getter 方法的get後面的部分,如:userViewModel.name。若是須要完成通知,則 ViewModel 類須要實現 BaseObservable 接口。databinding框架爲咱們提供了一些默認的基於基礎變量類型的綁定屬性對象能夠直接使用。(有時間整理源碼邏輯時整理一份)。在框架綁定通知實現中已經幫咱們處理了若是實現了雙向綁定,setter方法中寫 notifyPropertyChanged 引發的無限循環問題,因此能夠不用考慮這個問題。
- 若是使用jetpack 中的 liveData 來完成綁定,則須要使用另一套觀察者模式來接管更新,這裏 databinding 的觀察者模式不會通知到 livedata , 須要咱們在綁定 livedata 時使用具體的 ViewDataBinding 實現來調用
ViewDataBinding.setLifecycleOwner(this);
開啓。這局話能夠卸載 activity 的onCreate方法裏。(具體之後須要整理一份 jetpack ViewModel 資料)- 使用 jetpack 的 liveData 會檢查生命週期,而使用 BaseObservable 來完成綁定,賦值時並不會在框架級別檢查生命週期,須要自行處理,可是使用 BaseObservable 可使用方法來代替屬性,相對比 liveData 在某些場景中有更簡單的效果。
package com.jvup.model; import android.widget.BaseAdapter; //未支持到androidx的使用的是這個包名 //import android.databinding.BaseObservable; import androidx.databinding.BaseObservable; import com.jvup.model.BR; public class UserViewModel extends BaseObservable { // 直接使用 如:android:adapter="@{userViewModel.adapter}" public final BaseAdapter adapter = new SummerBindAdapter(); // 性別屬性 private boolean isMale = true; // 姓名屬性 private String name; public String headPortrait; // 遵循 javaBean 約定 如:android:src="@{userViewModel.head}" public String getHead() { return headPortrait; } // 這個是錯誤的不能用來接收性別屬性 // public void setSex(boolean isMale) { // this.isMale = isMale; // } public void setSex(String sex) { this.isMale = "男".equal(sex); } public String getSex() { return isMale ? "男" : "女"; } public void setName(String name) { this.name = name; // 用於通知控件更新指定ID的屬性。 notifyPropertyChanged(BR.name); } @Bindable public String getName() { return name; } }