DataBinding
1.什么是DataBinding
DataBinding: 是 google 為開發者提供的實用工具套庫 Jetpack 中的一個組件庫,他是基於APT(Annotation Processing Tool) 實現的一個MVVM框架庫。使用 DataBinding 我們可以很輕松的完成M-V 的雙向綁定。
// 例如這里與下面的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>
2.工作原理
DataBinding 是一個基於 APT 技術的MVVM框架,DataBinding 使用了觀察這模式來完成雙向通知,通過 APT 技術在編譯時掃描 XML 布局中的標記生成一個繼承自 ViewDataBinding 的子類並寫入相關綁定代碼,保存在build目錄下,不同版本的 android studio 具體路徑不一,可以通過切換成android工程目錄模式后在java(generated)目錄下查找,保存類名為布局名+BindingImpl(如上面的例子layout_user.xml 對應 LayoutUserBindingImpl)。並建立相關綁定事件ID(在BR資源中存放),通過ID可以主動發起相關更新事件通知。(關於詳細實現另做源碼分析)。ViewDataBinding 內部維護了觀察者注冊與通知並且持有 所有 View 對象以及綁定注冊的model對象。
3.如何使用
1)啟動 DataBinding
android studio 老版本
//build.gradle(:app) 那個Module使用就在那個module里寫
android {
dataBinding {
enabled = true
}
}
android studio 4.1之后版本
//build.gradle(:app) 那個Module使用就在那個module里寫
android {
buildFeatures {
dataBinding = true
}
}
2)具體使用
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;
}
}