從我的CSDN博客http://blog.csdn.net/dahaohan 遷移的第一篇博文祥款。
What's Data-Binding孙蒙?
看過(guò)我之前轉(zhuǎn)發(fā)的博文Android App的設(shè)計(jì)架構(gòu):MVC,MVP,MVVM經(jīng)驗(yàn)談
可以了解到移動(dòng)端App開(kāi)發(fā)架構(gòu)從傳統(tǒng)MVC-->MVP-->MVVM的一些進(jìn)展和演化,而目前發(fā)展成的MVVM架構(gòu)則需要使用Data-Binding機(jī)制來(lái)完成View和ViewModel之間的通信。
2015年google I/0開(kāi)發(fā)者大會(huì)發(fā)布的Data-binding庫(kù)邦危,使得開(kāi)發(fā)者可以更加簡(jiǎn)潔優(yōu)雅的編寫(xiě)代碼實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)邏輯脚翘,而不必去關(guān)注數(shù)據(jù)變更后UI View的更新問(wèn)題耻姥。View的變化會(huì)直接影響ViewModel秧了,ViewModel的變化或者內(nèi)容也會(huì)直接體現(xiàn)在View上翩瓜,這就是Data Binding Library默默幫您完成的工作受扳。
Android官方Data Binding介紹:https://developer.android.com/topic/libraries/data-binding/index.html
Data Binding Requirements
The Data Binding Library offers both flexibility and broad compatibility — it's a support library, so you can use it with all Android platform versions back to Android 2.1 (API level 7+).
To use data binding, Android Plugin for Gradle 1.5.0-alpha1 or higher is required.
Also, make sure you are using a compatible version of Android Studio. Android Studio 1.3 and later provides support for data binding.
Data binding 是一個(gè)類似support-v4/v7的支持庫(kù),API 7+都可以使用兔跌;
Android Studio1.3+
Android Gradle插件 1.5.0 +
How To Use Data Binding勘高?
要使用Data binding功能需要在你app module 的build.gradle中開(kāi)啟。
android {
....
dataBinding {
enabled = true
}
}
<h3>Data binding Layout
使用Data binding核心是將viewModel數(shù)據(jù)嵌入到布局layout xml文件坟桅,故而layout xml文件與之前的純view布局文件的構(gòu)成略有不同华望。
//以下為官方給出的最簡(jiǎn)單的一個(gè)示例:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="test.example.com.databindingtest.User">
</variable>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/firstname_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Data binding的布局文件的根節(jié)點(diǎn)為layout標(biāo)簽,由一個(gè)數(shù)據(jù)data標(biāo)簽以及一個(gè)根視圖標(biāo)簽構(gòu)成仅乓。
data標(biāo)簽聲明了該View使用的數(shù)據(jù)模型viewModel為變量名為user的User類對(duì)象立美,然后在Root View標(biāo)簽下通過(guò)
@{“變量名" + "." + ”屬性名/函數(shù)名“}來(lái)給指定的view設(shè)置值。
PS:
//對(duì)于User類的另一種寫(xiě)法
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
關(guān)于Data binding框架識(shí)別語(yǔ)句 @{user.firstName}
的一個(gè)機(jī)制是對(duì)于第一種寫(xiě)法訪問(wèn)User類的 firstName屬性方灾,而第二種寫(xiě)法則是訪問(wèn)User類的getFirstName()方法建蹄,或者也有可能訪問(wèn)firstName()方法如果存在的話。
<h3>Binding Data
編寫(xiě)完成data binding layout之后裕偿,類似IDE自動(dòng)編譯資源文件產(chǎn)生資源ID R文件洞慎,默認(rèn)將以layout xml文件的名字為基礎(chǔ)生成一個(gè)繼承自ViewDataBinding的類,此例xml文件為activity_main.xml故而生成ActivityMainBinding.java
生成的ViewDataBinding類默認(rèn)命名規(guī)則為嘿棘,布局xml文件名去掉"_"以駝峰式寫(xiě)法+“Binding”劲腿,當(dāng)然也支持自定義命名,后續(xù)提及
對(duì)于Android Studio IDE可以在
app/build/intermediates/classes/debug/+"對(duì)應(yīng)包目錄"+databinding文件夾下查看該生成的ViewDataBinding類
PS:注意這里生成的ActivityMainBinding內(nèi)的以下幾個(gè)屬性和函數(shù)
public class ActivityMainBinding extends android.databinding.ViewDataBinding {
..........
.....
// views
//在layout xml內(nèi)定義了id的 android:id="@+id/firstname_text"
//Viewdatabinding將會(huì)以駝峰命名生成對(duì)應(yīng):
//public final android.widget.TextView firstnameText;
//通過(guò)獲得的binding類以及view的id名稱可以直接獲取該view的引用鸟妙,而不需要再findViewById
public final android.widget.TextView firstnameText;
//未設(shè)置id的view則只會(huì)定義為private焦人,外部將不可訪問(wèn)
private final android.widget.LinearLayout mboundView0;
private final android.widget.TextView mboundView2;
//layout內(nèi)聲明的變量,對(duì)應(yīng)成員變量以及默認(rèn)以駝峰命名生成set/get函數(shù)setUser
//用于給dataBinding設(shè)置數(shù)據(jù)viewModel
// variables
private test.example.com.databindingtest.User mUser;
........
public void setUser(test.example.com.databindingtest.User user) {
this.mUser = user;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
public test.example.com.databindingtest.User getUser() {
return mUser;
}
........
..................
}
略微查看默認(rèn)生成的ActivityMainBinding類重父,可以看出binding類就是管理并維護(hù)View與View Model的關(guān)系的核心花椭。包括給將user數(shù)據(jù)對(duì)應(yīng)值賦值給對(duì)應(yīng)的view視圖;當(dāng)user內(nèi)值變化時(shí)更新視圖房午;視圖交互事件調(diào)用viewmodel的業(yè)務(wù)邏輯等矿辽。
ViewDataBinding相當(dāng)于一個(gè)聯(lián)系對(duì)應(yīng)layout中View與對(duì)應(yīng)ViewModel(data標(biāo)簽下的數(shù)據(jù))的框架,我們使用時(shí)需要將對(duì)應(yīng)的View和ViewModel實(shí)例對(duì)象傳遞給該ViewDataBinding框架,Data Binding庫(kù)已經(jīng)提供了多種方法來(lái)給實(shí)現(xiàn):
@Override
protected void onCreate(Bundle savedInstanceState) {
//獲取ViewDataBinding實(shí)例對(duì)象的同時(shí)已經(jīng)通過(guò)傳遞對(duì)應(yīng)的View對(duì)象或者layout文件參數(shù)袋倔,
//將對(duì)應(yīng)的View實(shí)例對(duì)象傳遞給該ViewDataBinding框架
//Activity最常用的取代之前的setContentView方法
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
//可直接通過(guò)id名稱訪問(wèn)對(duì)應(yīng)View變量
dataBinding.firstnameText.setText("hello");
//使用默認(rèn)生成的ActivityMainBinding直接bind對(duì)應(yīng)View
dataBinding = ActivityMainBinding.bind(viewRoot);
//或者如下
dataBinding = ActivityMainBinding.inflate(getLayoutInflater());
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
//給DataBinding框架設(shè)置viewModel數(shù)據(jù)源User
User user = new User("Test", "User");
//set方法根據(jù)layout中聲明的數(shù)據(jù)類型自動(dòng)生成
binding.setUser(user);
}
上述步驟完成運(yùn)行即可發(fā)現(xiàn)雕蔽,user各屬性數(shù)據(jù)自動(dòng)綁定到了各自的View上,但是這只體現(xiàn)了dataBinding單方面從ViewModel到View的數(shù)據(jù)綁定宾娜;如何實(shí)現(xiàn)user數(shù)據(jù)更新然后DataBinding自動(dòng)更新UiView批狐? View的交互事件如何綁定到ViewModel的業(yè)務(wù)邏輯?
Data Binding View Event Handling##
類似data binding提供的View的數(shù)據(jù)綁定前塔,在layout的view標(biāo)簽內(nèi)同樣允許使用表達(dá)式直接引用相應(yīng)的函數(shù)處理分發(fā)的事件贾陷。使用函數(shù)引用的View 屬性由對(duì)應(yīng)的事件listener的函數(shù)方法決定:
//View一般有View.OnLongClickListener/ View.OnClickListener
//對(duì)應(yīng)的兩個(gè)方法為onLongClick/onClick故而data binding在view標(biāo)簽下有如下屬性:
android:onClick="@{user.onLastNameClick}"
android:onLongClick="@{user.onLastNameLongClick}"
若在User類加入以下函數(shù),則在View內(nèi)完整引用監(jiān)聽(tīng)click事件寫(xiě)法如下:
public class User {
....
........
public void onLastNameClick(View v){
Log.d("Test"," onLastNameClick ");
}
public boolean onLastNameLongClick(View v){
Log.d("Test"," onLastNameLongClick ");
return false;
}
....
.......
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="test.example.com.databindingtest.User">
</variable>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/firstname_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.onLastNameClick}"
android:onLongClick="@{user.onLastNameClick}"/>
</LinearLayout>
</layout>
進(jìn)行編譯后運(yùn)行即可發(fā)現(xiàn)點(diǎn)擊交互事件可以順利觸發(fā)和處理嘱根。
Android官方對(duì)于View標(biāo)簽引用的事件處理函數(shù)的要求說(shuō)明:
In your expressions, you can reference methods that conform to the signature of the listener method. When an expression evaluates to a method reference, Data Binding wraps the method reference and owner object in a listener, and sets that listener on the target view. If the expression evaluates to null, Data Binding does not create a listener and sets a null listener instead.
官方說(shuō)明View處理事件引用的方法的簽名要與對(duì)應(yīng)的clickListener的方法簽名相符髓废,而方法的簽名側(cè)重的是方法名和方法參數(shù)的順序、類型该抒、個(gè)數(shù)慌洪;這里測(cè)試之后其實(shí)需要的是與對(duì)應(yīng)的clickListener的方法的參數(shù)以及返回值一致。
編譯期間將對(duì)View#onClick attribute的表達(dá)式引用的方法進(jìn)行合法性檢查凑保,若是方法參數(shù)/返回值對(duì)應(yīng)不上冈爹,則會(huì)出現(xiàn)編譯錯(cuò)誤:
Error:(26, 36) Listener class android.view.View.OnLongClickListener with method onLongClick did not match signature of any method user.onLastNameLongClick
若編譯正確,實(shí)質(zhì)其實(shí)還是將該方法包裝進(jìn)一個(gè)對(duì)應(yīng)的listener然后給view設(shè)置對(duì)應(yīng)監(jiān)聽(tīng)接口欧引。
Data Binding listening data/properties changes
Data binding真正的好處提現(xiàn)在data(user)數(shù)據(jù)變化時(shí)频伤,能夠自動(dòng)更新對(duì)應(yīng)的UI顯示,如何使用這個(gè)核心功能呢芝此?
<h3>Observable 對(duì)象
private static class User extends BaseObservable {
private String firstName;
private String lastName;
//Bindable注解告訴data binding框架需要偵聽(tīng)該值的變化
//編譯期間生成的類似R文件的一個(gè)BR文件將會(huì)有該屬性的一個(gè)public資源Id
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
//通過(guò)對(duì)應(yīng)屬性的資源ID告訴data binding框架該屬性發(fā)生變化需要更新ui什么的
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
限定了修改屬性數(shù)據(jù)的方法在對(duì)應(yīng)的set方法內(nèi)憋肖,所以在set方法更新屬性后通知data binding框架屬性的更新即可。
<h3>ObservableFields
其實(shí)與上述的BaseObservable對(duì)象是類似的原理婚苹,只是將整個(gè)類的范圍縮小到個(gè)別需要偵聽(tīng)的屬性上岸更,提供了ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
user.firstName.set("Google");
int age = user.age.get();
事實(shí)上每一個(gè)ObservableFields都繼承自BaseObservable包含了單獨(dú)一個(gè)屬性值,內(nèi)部默認(rèn)封裝了set/get方法膊升,將上述BaseObservable的方法封裝好了怎炊,也是在set方法之后通知data-binding框架做出一些更新操作。
<h3>Observable Collections
對(duì)于一些需要使用到list/map等集合數(shù)據(jù)類型來(lái)說(shuō)有: ObservableArrayMap廓译,ObservableArrayList等
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
PS: Android Studio IDE目前并不能很完美的支持到data標(biāo)簽的一些變量的import或者是類型聲明评肆;但是并不會(huì)影響編譯運(yùn)行。
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
<data>
<import type="android.databinding.ObservableList"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text="@{user[0]}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{String.valueOf(1 + (Integer)user[2])}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Some Details
關(guān)于data標(biāo)簽支持import類似java的import非区,以及其對(duì)于一些類似map/list/array等集合類型的數(shù)據(jù)的語(yǔ)法簡(jiǎn)要介紹:
//下面的 < 為符號(hào)'<'字符實(shí)體
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
//集合類型數(shù)據(jù)都是類似數(shù)組使用的'[]'訪問(wèn)特定的數(shù)據(jù)瓜挽,對(duì)于數(shù)組[]內(nèi)是對(duì)應(yīng)的下標(biāo),對(duì)于map則是對(duì)應(yīng)的key值
PS: Map的key為一個(gè)String時(shí)院仿,可能遇到引號(hào)內(nèi)要使用引號(hào)包括字符串的沖突秸抚,這時(shí)使用:
//單引號(hào)在外包括速和,內(nèi)部使用雙引號(hào)標(biāo)示字符串
android:text='@{map["firstName"]}'
//或者外部使用雙引號(hào)歹垫,內(nèi)部用back quote反引號(hào)標(biāo)示字符串(反引號(hào)即'~'按鍵)
android:text="@{map[`firstName`}"
//或者使用"即雙引號(hào)的java字符實(shí)體來(lái)替代雙引號(hào)
android:text="@{map["firstName"]}"
About More Details
關(guān)于data binding的細(xì)節(jié)知識(shí)推薦閱讀:
官方介紹文檔:https://developer.android.com/topic/libraries/data-binding/index.html
比較全面的官方文檔的翻譯檔:http://www.reibang.com/p/b1df61a4df77
結(jié)合實(shí)例的介紹:https://github.com/LyndonChin/MasteringAndroidDataBinding