還記得在博客[《高逼格UI-ASD(Android Support Design)》](http://blog.csdn.net/qibin0506/article/details/46850763)的開始曾經說過,Android最新推出了一個官方的數據綁定框架-Data Binding Library。現在github上也有很多三方的數據綁定框架,但是我們為什么要選擇官方的呢?恩,答對了。就是因為是官方的,三方的東西說不定什么時候作者一步高興就停止更新了,官方的就不一樣了,我們可以看到它漸漸的穩定起來。好了廢話不多說,從這篇博客開始,我們就來了解一下android最新給我們帶來的數據綁定框架——Data Binding Library。數據綁定框架給我們帶來了更大的方便性,以前我們可能需要在`Activity`里寫很多的`findViewById`,煩人的代碼也增加了我們代碼的耦合性,現在我們馬上就可以拋棄*那么多*的`findViewById`。說到這里,有人可能會有個疑問:我使用一些注解框架也可以不用`findViewById`啊,是的,但是注解注定要拖慢我們代碼的速度,Data Binding則不會,官網文檔說*還會提高解析XML的速度*,最主要的Data Binding并不是單單減少了我們的`findViewById`,更多好處請往下看文章。
一、環境
在開始使用新東西之前,我們需要稍微的配置一下環境,這里要求你的Android Studio版本是**1.3+**,使用eclipse的同學暫時還沒有辦法使用該框架,請換用Android Studio。還有,在開始之前,請更新你的`Support repository`到最新的版本。
萬事俱備,那我們就開始搭配環境!
新建一個`project`,在`dependencies`中添加以下依賴
~~~
classpath "com.android.databinding:dataBinder:1.0-rc1"
~~~
新建`module`,并且在`module`的build.gradle文件中添加
~~~
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
~~~
ok,到現在為止,我們的環境就準備完畢了,下面我們就開始Data Binding的學習啦。
二、Data Binding嘗試
在代碼開始,我們并不直接進入新東西的講解,而且以一段代碼展現Data Binding的魅力。
首先我們需要一個`java bean`,很簡單,一個學生類。
~~~
public class Student {
private String name;
private String addr;
public Student() {
}
public Student(String name, String addr) {
this.name = name;
this.addr = addr;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return this.addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
~~~
再來看看我們布局文件怎么寫:
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="stu"
type="org.loader.androiddatabinding.Student" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.addr}"/>
</LinearLayout>
</layout>
~~~
可以看到我們的xml布局和以前還有有一定的差別的,但是差別也不是很大。
最后來看看`Activity`怎么寫。
~~~
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setStu(new Student("loader", "山東萊蕪"));
}
}
~~~
`Activity`的代碼非常簡單,就添加了兩行代碼,而且,值得注意的是:我們并沒有`findViewById`然后再去`setText`。
這段小代碼運行的結果大家可能已經猜到了,就是在界面上顯示`loader`和`山東萊蕪`兩句話。

)
在看完小實例后,大家是不是感覺棒棒噠? 沒有了之前的find控件,沒有了setText,`Activity`代碼更加簡潔明了!
下面開始,我們進入Data Binding的學習!
三、 初始Data Binding
上面的代碼算是帶領我們進入了Data Binding的世界,那我們先從布局文件開始入手Data Binding吧。再來看看上面的布局文件。
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="stu"
type="org.loader.androiddatabinding.Student" />
</data>
...
</layout>
~~~
我們的根節點變成了`layout`,在`layout`的子節點中分成兩部分,第一部分是`data`節點,第二部分才是我們之前的根節點,在`data`節點下我們又定義了一個`variable`,
從名稱上看,這應該是一個變量,變量的名稱是`stu`,類型是`org.loader.androiddatabinding.Student`,這類似我們在java文件中這么定義:
~~~
org.loader.androiddatabinding.Student stu;
~~~
ok,這樣很好理解了吧,不過這里要寫`Student`完整的包名,一個還好,如果這里我們需要多個`Student`呢?要累死? NO,NO,NO,我們還可以向寫java文件那樣導入包。
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="org.loader.app2.Student" />
<variable
name="stu"
type="Student" />
</data>
...
</layout>
~~~
這樣寫,就類似于java的
~~~
import org.loader.app2.Student;
...
Student stu;
...
~~~
既然變量我們定義好了,那該怎么使用呢?還是看上面的xml文件。
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
...
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.addr}"/>
</LinearLayout>
</layout>
~~~
恩,注意看兩個`TextView`的`android:text`,它的值是一個以`@`開始,以{}包裹的形式出現,而內容呢?是`stu.name`。stu就是我們上面定義的`variable`,
name還記得嗎?是我們`Student`類中的一個變量。其實這里就會去調用`stu.getName()`方法。
好了,很快,我們就入門了Data Binding,下面讓我們來多定義幾個變量試試看。
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="org.loader.app2.Student" />
<variable
name="stu"
type="Student" />
<variable
name="str"
type="String"/>
<variable
name="error"
type="boolean"/>
<variable
name="num"
type="int" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{str}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(num)}"/>
</LinearLayout>
</layout>
~~~
來看看定義的變量,多了好幾個,有一個`String`類型的變量我們并沒有導包,這里說明一下,和在java里一樣,`java.lang`包里的類,我們是可以不用導包的,再往下,一個`boolean`和`int`類型的變量,都是java基本類型的,所以說嘛,在這里定義變量,你就想成是在java里定義就ok。
再來看看這幾個`TextView`,第二個,我們直接使用`@{str}`來為`android:text`設置成上面定義個`str`的值,繼續往下要注意了,我們使用了
~~~
android:text="@{String.valueOf(num)}"
~~~
來設置了一個`int`類型的變量,大家都知道我們在給`android:text`設置`int`類型的值時一定要轉化為`String`類型,要不它就認為是資源文件了,這里我們還學到了一點,在xml中,我們不僅可以使用變量,而且還可以調用方法!
四、 變量定義的高級部分
在上面,我們學會了如何去在xml中定義變量,但是不知道你發現沒?我們沒有定義像`List`、`Map`等這樣的集合變量。那到底能不能定義呢?答案肯定是可以的,而且定義的方式和我們上面的基本一致,區別就在于我們還需要為它定義key的變量,例如:
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="org.loader.app2.Student" />
<import type="android.graphics.Bitmap" />
<import type="java.util.ArrayList" />
<import type="java.util.HashMap" />
<variable
name="stu"
type="Student" />
<variable
name="str"
type="String"/>
<variable
name="error"
type="boolean"/>
<variable
name="num"
type="int" />
<variable
name="list"
type="ArrayList<String>" />
<variable
name="map"
type="HashMap<String, String>" />
<variable
name="array"
type="String[]" />
<variable
name="listKey"
type="int" />
<variable
name="mapKey"
type="String" />
<variable
name="arrayKey"
type="int" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{str}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(num)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[listKey]}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`name`]}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{array[0]}"/>
</LinearLayout>
</layout>
~~~
這段代碼比較長,但是我們僅關心那幾個集合和數組,可以看到我們定義集合和定義普通變量一樣,只不過這里我們還指定了一些的泛型,例如:`ArrayList<String>`。
下面我們還為下面使用這些集合準備了幾個key,也都是變量。
繼續看看怎么使用,和我們在java中使用不同,這里都是以:集合變量名[key]的形式使用,如果你的key是一個字面字符串可以使用反引號,也可以使用轉義后的雙引號。恩,這里也沒有什么可以說的了,大家多看幾遍就掌握了,都是概念性的東西,記住就ok。
五、在java代碼中使用
前面定義了這么多變量,但是我們還沒有給他們賦值!在哪賦值呢?肯定是在java代碼中使用了,大部分情況我們還是在`Activity`中去使用它,以前我們都是在`onCreate`方法中通過`setContentView`去設置布局,但現在不一樣了,現在我們是用過`DataBindingUtil`類的一個靜態方法`setContentView`設置布局,同時該方法會返回一個對象,什么對象?這個對象有點特殊,它是一個自動生成的類的對象,看下面:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
}
~~~
看到`ActivityMainBinding`了嗎?就是它!那自動生成有什么規則了沒?當然有了,記好了
> 將我們布局文件的首字母大寫,并且去掉下劃線,將下劃線后面的字母大寫,加上Binding組成。
看看上面的類,是不是符合這個規則。繼續看看這個對象哪來的,是通過
~~~
DataBindingUtil.setContentView(this, R.layout.activity_main);
~~~
返回的,DataBindingUtil.setContentView的兩個參數分別是當前`Activity`和布局文件。那接下來,就是我們關心的給變量賦值了。
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
...
binding.setStu(new Student("loader"));
binding.setStr("string");
binding.setError(false);
ArrayList<String> list = new ArrayList<String>() {
{
add("arraylist");
}
};
binding.setList(list);
binding.setListKey(0);
HashMap<String, String> map = new HashMap<String, String>() {
{
put("name", "hashmap");
}
};
binding.setMap(map);
// binding.setMapKey("name");
String[] array = new String[1];
array[0] = "array";
binding.setArray(array);
binding.setArrayKey(0);
}
~~~
一連串的binding.setXXX,這個XXX是什么呢?就是我們在xml中定義的那些變量首字母大寫了!也沒好好說的吧,多看幾遍。
六、 表達式
短暫的幸福時光,我們還是要告別java代碼了,繼續回到xml中,這一塊,我們來學習一下表達式,什么?這玩意在xml中還支持表達式!
~~~
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{error ? "error" : "ok"}'/>
~~~
還記得上面我們定義了一個boolean的變量沒有用到,這里我們就用到了,看好`android:text`,這里是一個三元表達式,如果error是true,則text就是error,否則是ok。這里還支持null合并操作,什么是null合并,相信看一眼你就知道了
~~~
android:text='@{str==null ?? "not null"}'
~~~
簡單解釋一下,如果str是null,text的值就是str本身,否則就是”not null”。
它還支持一下表達式:
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
但是它不支持一下表達式:
- this
- super
- new
- Explicit generic invocation
七、 其他遺漏點
說到這里,xml中的事情基本算完了,但是還有幾個小地方沒有說,順便說一下。
1. 設置別名
假如我們import了兩個相同名稱的類咋辦?別怕,別名來拯救你!例如:
~~~
...
<data>
<import type="xxx.Name" alias="MyName">
<import type="xxx.xx.Name">
</data>
<TextView xxx:@{MyName.getName()}>
<TextView xxx:@{Name.getName()}>
...
~~~
1. 自定義Binding名稱
還記得系統為我們生成好的那個binding類名嗎?如果只能使用那樣的是不是有點太暴力了?好在google對我們還算友好了,允許我們自定義binding名稱,定制名稱也很簡單,就是給data一個class字段就ok。
例如:
~~~
<data class=".Custom">
...
</data>
~~~
那么:DataBindingUtils.setContentView返回的binding類就是:`你的應用包名.Custom`。
八、事件綁定
大家都知道,在xml中我們可以給`button`設置一個`onClick`來達到事件的綁定,現在DataBinding也提供了事件綁定,而且不僅僅是`button`。
來看一下:
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="org.loader.app3.EventHandlers" />
<variable
name="handlers"
type="EventHandlers" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CLICK ME"
android:onClick="@{handlers.handleClick}"/>
</LinearLayout>
</layout>
~~~
定義了一個`EventHandlers`類型的`handlers`變量,并在onClick的時候執行`EventHandlers`的`handleClick`方法。
繼續看看EventHandlers是怎么寫的。
~~~
public class EventHandlers {
public void handleClick(View view) {
Toast.makeText(view.getContext(), "you clicked the view", Toast.LENGTH_LONG).show();
}
}
~~~
很簡單,就是簡單的`Toast`了一下,這里要注意的是,`handlerClick`方法需要一個`View`的參數。
九、 數據對象
我們學會了通過binding為我們的變量設置數據,但是不知道你有沒有發現一個問題,當我們數據改變的時候會怎樣?數據是跟隨著改變呢?還是原來的數據呢?這里告訴你答案:很不幸,顯示的還是原來的數據?那有沒有辦法讓數據源發生變化后顯示的數據也隨之發生變化?先來想想`ListView`是怎么做的, `ListView`的數據是通過`Adapter`提供的,當數據發生改變時,我們通過`notifyDatasetChanged`通過UI去改變數據,這里面的原理其實就是內容觀察者,慶幸的是DataBinding也支持內容觀察者,而且使用起來也相當方便!
BaseObservable
我們可以通過Observable的方式去通知UI數據已經改變了,當然了,官方為我們提供了更加簡便的方式`BaseObservable`,我們的實體類只需要繼承該類,稍做幾個操作,就能輕松實現數據變化的通知。如何使用呢? 首先我們的實體類要繼承`BaseObservale`類,第二步在`Getter`上使用注解`@Bindable`,第三步,在`Setter`里調用方法`notifyPropertyChanged`,第四步,完成。就是這么簡單,下面我們來實際操作一下。
首先定義一個實體類,并繼承`BaseObservable`
~~~
public class Student extends BaseObservable {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(org.loader.app4.BR.name);
}
}
~~~
觀察getName方法,我們使用了`@Bindable`注解,觀察setName,我們調用了`notifyPropertyChanged`方法,這個方法還需要一個參數,這里參數類似于`R.java`,保存了我們所有變量的引用地址,這里我們使用了name。
再來看看布局文件。
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<import type="org.loader.app4.Student" />
<variable
name="stu"
type="Student"/>
<variable
name="click"
type="org.loader.app4.MainActivity" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{click.click}"
android:text="@{stu.name}"/>
</layout>
~~~
不多說了,我們給`TextView`設置了文本,還有點擊事件。Activity,
~~~
public class MainActivity extends AppCompatActivity {
private Student mStu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
org.loader.app4.Custom binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
mStu = new Student("loader");
binding.setStu(mStu);
binding.setClick(this);
}
public void click(View view) {
mStu.setName("qibin");
}
}
~~~
這段代碼,首先顯示的是loader,當我們點擊`TextView`時,界面換成qibin。
ObservableFields家族
上面使用`BaseObservable`已經非常容易了,但是google工程師還不滿足,繼續給我們封裝了一系列的`ObservableFields`,這里有`ObservableField`,`ObservableBoolean`,`ObservableByte`,`ObservableChar`,`ObservableShort`,`ObservableInt`,`ObservableLong`,`ObservableFloat`,`ObservableDouble`,`ObservableParcelable`
ObservableFields的使用方法就更加簡單了,例如下面代碼,
~~~
public class People {
public ObservableField<String> name = new ObservableField<>();
public ObservableInt age = new ObservableInt();
public ObservableBoolean isMan = new ObservableBoolean();
}
~~~
很簡單,只有三個ObservableField變量,并且沒有getter和setter,因為我們不需要getter和setter。
在xml中怎么使用呢?
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="people"
type="org.loader.app4.People" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(people.age)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{people.isMan ? "man" : "women"}'/>
</LinearLayout>
</layout>
~~~
也很簡單,直接使用變量,那怎么賦值和取值呢?這些ObservableField都會有一對`get`和`set`方法,所以使用起來也很方便了:
~~~
...
mPeople = new People();
binding.setPeople(mPeople);
mPeople.name.set("people");
mPeople.age.set(19);
mPeople.isMan.set(true);
...
~~~
也不多說了。
Observable Collections
既然普通的變量我們有了ObservableFields的分裝,那集合呢?當然也有啦,來看著兩個:`ObservableArrayMap`,`ObservableArrayList`。使用和普通的Map、List基本相同,直接看代碼:
~~~
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="map"
type="android.databinding.ObservableArrayMap<String,String>" />
<variable
name="list"
type="android.databinding.ObservableArrayList<String>" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`name`]}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[0]}"/>
</LinearLayout>
</layout>
~~~
在布局中,使用方式和普通的集合一樣,如果看不太懂,可以往上翻博客,看上面的集合是怎么使用的。
在來看java文件,怎么設置數據,
~~~
ObservableArrayMap<String, String> map = new ObservableArrayMap<>();
ObservableArrayList<String> list = new ObservableArrayList<>();
map.put("name", "loader or qibin");
list.add("loader!!!");
binding.setMap(map);
binding.setList(list);
~~~
哦,太簡單了,簡直和`List`、`Map`使用方法一模一樣!!!
好了,不多說了,大家也都看累了吧。 那就先說到這里,其他的下篇博客我們繼續學習。
[demo源碼下載,戳這里](http://download.csdn.net/detail/qibin0506/8983229)
- 前言
- 高逼格UI-ASD(Android Support Design)
- AndroidSupportDesign之TabLayout使用詳解
- RecyclerView的高級用法——定制動畫
- Android官方數據綁定框架DataBinding(一)
- Android官方數據綁定框架DataBinding(二)
- 你所不知道的Activity轉場動畫——ActivityOptions
- RecyclerView+ImageLoader打造多選圖庫
- Android Material Design動畫
- RecyclerView添加Header的正確方式
- CoordinatorLayout高級用法-自定義Behavior