Android官方數據綁定框架DataBinding用法詳解+附帶DEMO源碼


今天來了解一下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中添加以下依賴

  1. classpath "com.android.databinding:dataBinder:1.0-rc1"  

新建module,並且在module的build.gradle文件中添加

  1. apply plugin: 'com.android.application'  
  2. apply plugin: 'com.android.databinding'  

ok,到現在爲止,我們的環境就準備完畢了,下面我們就開始Data Binding的學習啦。

二、Data Binding嘗試 
在代碼開始,我們並不直接進入新東西的講解,而且以一段代碼展現Data Binding的魅力。 
首先我們需要一個Java bean,很簡單,一個學生類。

  1. public class Student {  
  2.     private String name;  
  3.     private String addr;  
  4.   
  5.     public Student() {  
  6.     }  
  7.   
  8.     public Student(String name, String addr) {  
  9.         this.name = name;  
  10.         this.addr = addr;  
  11.     }  
  12.   
  13.     public String getName() {  
  14.         return name;  
  15.     }  
  16.   
  17.     public void setName(String name) {  
  18.         this.name = name;  
  19.     }  
  20.   
  21.     public String getAddr() {  
  22.         return this.addr;  
  23.     }  
  24.   
  25.     public void setAddr(String addr) {  
  26.         this.addr = addr;  
  27.     }  
  28. }  

再來看看我們佈局文件怎麼寫:

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <variable  
  4.             name="stu"  
  5.             type="org.loader.androiddatabinding.Student" />  
  6.     </data>  
  7.   
  8.     <LinearLayout  
  9.         android:orientation="vertical"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content">  
  12.         <TextView  
  13.             android:layout_width="wrap_content"  
  14.             android:layout_height="wrap_content"  
  15.             android:text="@{stu.name}"/>  
  16.   
  17.         <TextView  
  18.             android:layout_width="wrap_content"  
  19.             android:layout_height="wrap_content"  
  20.             android:text="@{stu.addr}"/>  
  21.     </LinearLayout>  
  22. </layout>  

可以看到我們的xml佈局和以前還有有一定的差別的,但是差別也不是很大。 
最後來看看Activity怎麼寫。

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  
  7.         binding.setStu(new Student("loader""山東萊蕪"));  
  8.     }  
  9. }  

Activity的代碼非常簡單,就添加了兩行代碼,而且,值得注意的是:我們並沒有findViewById然後再去setText。 
這段小代碼運行的結果大家可能已經猜到了,就是在界面上顯示loader山東萊蕪兩句話。

)

在看完小實例後,大家是不是感覺棒棒噠? 沒有了之前的find控件,沒有了setText,Activity代碼更加簡潔明瞭! 
下面開始,我們進入Data Binding的學習!

三、 初始Data Binding 
上面的代碼算是帶領我們進入了Data Binding的世界,那我們先從佈局文件開始入手Data Binding吧。再來看看上面的佈局文件。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <variable  
  4.             name="stu"  
  5.             type="org.loader.androiddatabinding.Student" />  
  6.     </data>  
  7.     ...  
  8. </layout>  

我們的根節點變成了layout,在layout的子節點中分成兩部分,第一部分是data節點,第二部分纔是我們之前的根節點,在data節點下我們又定義了一個variable, 
從名稱上看,這應該是一個變量,變量的名稱是stu,類型是org.loader.androiddatabinding.Student,這類似我們在java文件中這麼定義:

  1. org.loader.androiddatabinding.Student stu;  
ok,這樣很好理解了吧,不過這裏要寫Student完整的包名,一個還好,如果這裏我們需要多個Student呢?要累死? NO,NO,NO,我們還可以向寫java文件那樣導入包。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <import type="org.loader.app2.Student" />  
  4.         <variable  
  5.             name="stu"  
  6.             type="Student" />  
  7.     </data>  
  8.     ...  
  9. </layout>  

這樣寫,就類似於java的

import org.loader.app2.Student;...Student stu;...

既然變量我們定義好了,那該怎麼使用呢?還是看上面的xml文件。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   ...  
  3.   <LinearLayout  
  4.       android:orientation="vertical"  
  5.       android:layout_width="match_parent"  
  6.       android:layout_height="wrap_content">  
  7.       <TextView  
  8.           android:layout_width="wrap_content"  
  9.           android:layout_height="wrap_content"  
  10.           android:text="@{stu.name}"/>  
  11.   
  12.       <TextView  
  13.           android:layout_width="wrap_content"  
  14.           android:layout_height="wrap_content"  
  15.           android:text="@{stu.addr}"/>  
  16.   </LinearLayout>  
  17. </layout>  

恩,注意看兩個TextViewandroid:text,它的值是一個以@開始,以{}包裹的形式出現,而內容呢?是stu.name。stu就是我們上面定義的variable
name還記得嗎?是我們Student類中的一個變量。其實這裏就會去調用stu.getName()方法。 
好了,很快,我們就入門了Data Binding,下面讓我們來多定義幾個變量試試看。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <import type="org.loader.app2.Student" />  
  4.         <variable  
  5.             name="stu"  
  6.             type="Student" />  
  7.         <variable  
  8.             name="str"  
  9.             type="String"/>  
  10.         <variable  
  11.             name="error"  
  12.             type="boolean"/>  
  13.         <variable  
  14.             name="num"  
  15.             type="int" />  
  16.     </data>  
  17.   
  18.     <LinearLayout  
  19.         android:orientation="vertical"  
  20.         android:layout_width="match_parent"  
  21.         android:layout_height="wrap_content">  
  22.         <TextView  
  23.             android:layout_width="wrap_content"  
  24.             android:layout_height="wrap_content"  
  25.             android:text="@{stu.name}"/>  
  26.         <TextView  
  27.             android:layout_width="wrap_content"  
  28.             android:layout_height="wrap_content"  
  29.             android:text="@{str}"/>  
  30.         <TextView  
  31.             android:layout_width="wrap_content"  
  32.             android:layout_height="wrap_content"  
  33.             android:text="@{String.valueOf(num)}"/>  
  34.     </LinearLayout>  
  35. </layout>  

來看看定義的變量,多了好幾個,有一個String類型的變量我們並沒有導包,這裏說明一下,和在java裏一樣,java.lang包裏的類,我們是可以不用導包的,再往下,一個booleanint類型的變量,都是java基本類型的,所以說嘛,在這裏定義變量,你就想成是在java裏定義就ok。 
再來看看這幾個TextView,第二個,我們直接使用@{str}來爲android:text設置成上面定義個str的值,繼續往下要注意了,我們使用了

android:text="@{String.valueOf(num)}"

來設置了一個int類型的變量,大家都知道我們在給android:text設置int類型的值時一定要轉化爲String類型,要不它就認爲是資源文件了,這裏我們還學到了一點,在xml中,我們不僅可以使用變量,而且還可以調用方法!

四、 變量定義的高級部分 
在上面,我們學會了如何去在xml中定義變量,但是不知道你發現沒?我們沒有定義像ListMap等這樣的集合變量。那到底能不能定義呢?答案肯定是可以的,而且定義的方式和我們上面的基本一致,區別就在於我們還需要爲它定義key的變量,例如:

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <import type="org.loader.app2.Student" />  
  4.         <import type="android.graphics.Bitmap" />  
  5.         <import type="java.util.ArrayList" />  
  6.         <import type="java.util.HashMap" />  
  7.         <variable  
  8.             name="stu"  
  9.             type="Student" />  
  10.         <variable  
  11.             name="str"  
  12.             type="String"/>  
  13.         <variable  
  14.             name="error"  
  15.             type="boolean"/>  
  16.         <variable  
  17.             name="num"  
  18.             type="int" />  
  19.         <variable  
  20.             name="list"  
  21.             type="ArrayList<String>" />  
  22.         <variable  
  23.             name="map"  
  24.             type="HashMap<String, String>" />  
  25.         <variable  
  26.             name="array"  
  27.             type="String[]" />  
  28.   
  29.         <variable  
  30.             name="listKey"  
  31.             type="int" />  
  32.         <variable  
  33.             name="mapKey"  
  34.             type="String" />  
  35.         <variable  
  36.             name="arrayKey"  
  37.             type="int" />  
  38.     </data>  
  39.   
  40.     <LinearLayout  
  41.         android:orientation="vertical"  
  42.         android:layout_width="match_parent"  
  43.         android:layout_height="wrap_content">  
  44.         <TextView  
  45.             android:layout_width="wrap_content"  
  46.             android:layout_height="wrap_content"  
  47.             android:text="@{stu.name}"/>  
  48.         <TextView  
  49.             android:layout_width="wrap_content"  
  50.             android:layout_height="wrap_content"  
  51.             android:text="@{str}"/>  
  52.         <TextView  
  53.             android:layout_width="wrap_content"  
  54.             android:layout_height="wrap_content"  
  55.             android:text="@{String.valueOf(num)}"/>  
  56.         <TextView  
  57.             android:layout_width="wrap_content"  
  58.             android:layout_height="wrap_content"  
  59.             android:text="@{list[listKey]}"/>  
  60.   
  61.         <TextView  
  62.             android:layout_width="wrap_content"  
  63.             android:layout_height="wrap_content"  
  64.             android:text="@{map[`name`]}"/>  
  65.         <TextView  
  66.             android:layout_width="wrap_content"  
  67.             android:layout_height="wrap_content"  
  68.             android:text="@{array[0]}"/>  
  69.     </LinearLayout>  
  70. </layout>  

這段代碼比較長,但是我們僅關心那幾個集合和數組,可以看到我們定義集合和定義普通變量一樣,只不過這裏我們還指定了一些的泛型,例如:ArrayList&lt;String>。 
下面我們還爲下面使用這些集合準備了幾個key,也都是變量。 
繼續看看怎麼使用,和我們在java中使用不同,這裏都是以:集合變量名[key]的形式使用,如果你的key是一個字面字符串可以使用反引號,也可以使用轉義後的雙引號。恩,這裏也沒有什麼可以說的了,大家多看幾遍就掌握了,都是概念性的東西,記住就ok。

五、在java代碼中使用 
前面定義了這麼多變量,但是我們還沒有給他們賦值!在哪賦值呢?肯定是在java代碼中使用了,大部分情況我們還是在Activity中去使用它,以前我們都是在onCreate方法中通過setContentView去設置佈局,但現在不一樣了,現在我們是用過DataBindingUtil類的一個靜態方法setContentView設置佈局,同時該方法會返回一個對象,什麼對象?這個對象有點特殊,它是一個自動生成的類的對象,看下面:

  1. @Override  
  2.    protected void onCreate(Bundle savedInstanceState) {  
  3.        super.onCreate(savedInstanceState);  
  4.        ActivityMainBinding binding = DataBindingUtil.setContentView(this,  
  5.                R.layout.activity_main);  
  6.     }  

看到ActivityMainBinding了嗎?就是它!那自動生成有什麼規則了沒?當然有了,記好了:

將我們佈局文件的首字母大寫,並且去掉下劃線,將下劃線後面的字母大寫,加上Binding組成。

看看上面的類,是不是符合這個規則。繼續看看這個對象哪來的,是通過

  1. DataBindingUtil.setContentView(this, R.layout.activity_main);  

返回的,DataBindingUtil.setContentView的兩個參數分別是當前Activity和佈局文件。那接下來,就是我們關心的給變量賦值了。

  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.    ...  
  4.     binding.setStu(new Student("loader"));  
  5.     binding.setStr("string");  
  6.     binding.setError(false);  
  7.   
  8.     ArrayList<String> list = new ArrayList<String>() {  
  9.         {  
  10.             add("arraylist");  
  11.         }  
  12.     };  
  13.     binding.setList(list);  
  14.     binding.setListKey(0);  
  15.   
  16.     HashMap<String, String> map = new HashMap<String, String>() {  
  17.         {  
  18.             put("name""hashmap");  
  19.         }  
  20.     };  
  21.     binding.setMap(map);  
  22. //        binding.setMapKey("name");  
  23.   
  24.     String[] array = new String[1];  
  25.     array[0] = "array";  
  26.     binding.setArray(array);  
  27.     binding.setArrayKey(0);  
  28. }  

一連串的binding.setXXX,這個XXX是什麼呢?就是我們在xml中定義的那些變量首字母大寫了!也沒好好說的吧,多看幾遍。

六、 表達式 
短暫的幸福時光,我們還是要告別java代碼了,繼續回到xml中,這一塊,我們來學習一下表達式,什麼?這玩意在xml中還支持表達式!

  1. <TextView  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content"  
  4.     android:text='@{error ? "error" : "ok"}'/>  

還記得上面我們定義了一個boolean的變量沒有用到,這裏我們就用到了,看好android:text,這裏是一個三元表達式,如果error是true,則text就是error,否則是ok。這裏還支持null合併操作,什麼是null合併,相信看一眼你就知道了

  1. 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了兩個相同名稱的類咋辦?別怕,別名來拯救你!例如:

  1. ...  
  2. <data>  
  3.   <import type="xxx.Name" alias="MyName">  
  4.   <import type="xxx.xx.Name">  
  5. </data>  
  6. <TextView xxx:@{MyName.getName()}>  
  7. <TextView xxx:@{Name.getName()}>  
  8. ...  

  1. 自定義Binding名稱 
    還記得系統爲我們生成好的那個binding類名嗎?如果只能使用那樣的是不是有點太暴力了?好在google對我們還算友好了,允許我們自定義binding名稱,定製名稱也很簡單,就是給data一個class字段就ok。 
    例如:
<span
 class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: 
border-box; font-family: 'Source Code Pro', monospace; font-size: 14px; 
line-height: 20px; white-space: pre; background-color: rgba(128, 128, 
128, 0.0470588);"><<span class="hljs-title" style="box-sizing: 
border-box; color: rgb(0, 0, 136);">data</span> <span 
class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0,
 102);">class</span>=<span class="hljs-value" 
style="box-sizing: border-box; color: rgb(0, 136, 
0);">".Custom"</span>></span><span style="color: 
rgb(51, 51, 51); font-family: 'Source Code Pro', monospace; font-size: 
14px; line-height: 20px; white-space: pre; background-color: rgba(128, 
128, 128, 0.0470588);">...</span><span class="hljs-tag" 
style="color: rgb(0, 102, 102); box-sizing: border-box; font-family: 
'Source Code Pro', monospace; font-size: 14px; line-height: 20px; 
white-space: pre; background-color: rgba(128, 128, 128, 
0.0470588);"></<span class="hljs-title" style="box-sizing: 
border-box; color: rgb(0, 0, 
136);">data</span>></span>

那麼:DataBindingUtils.setContentView返回的binding類就是:你的應用包名.Custom

八、事件綁定 
大家都知道,在xml中我們可以給button設置一個onClick來達到事件的綁定,現在DataBinding也提供了事件綁定,而且不僅僅是button。 
來看一下:

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <import type="org.loader.app3.EventHandlers" />  
  4.         <variable  
  5.             name="handlers"  
  6.             type="EventHandlers" />  
  7.     </data>  
  8.   
  9.     <LinearLayout  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         android:orientation="vertical">  
  13.         <TextView  
  14.             android:layout_width="wrap_content"  
  15.             android:layout_height="wrap_content"  
  16.             android:text="CLICK ME"  
  17.             android:onClick="@{handlers.handleClick}"/>  
  18.     </LinearLayout>  
  19. </layout>  

定義了一個EventHandlers類型的handlers變量,並在onClick的時候執行EventHandlershandleClick方法。 
繼續看看EventHandlers是怎麼寫的。

  1. public class EventHandlers {  
  2.     public void handleClick(View view) {  
  3.         Toast.makeText(view.getContext(), "you clicked the view", Toast.LENGTH_LONG).show();  
  4.     }  
  5. }  

很簡單,就是簡單的Toast了一下,這裏要注意的是,handlerClick方法需要一個View的參數。

九、 數據對象 
我們學會了通過binding爲我們的變量設置數據,但是不知道你有沒有發現一個問題,當我們數據改變的時候會怎樣?數據是跟隨着改變呢?還是原來的數據呢?這裏告訴你答案:很不幸,顯示的還是原來的數據?那有沒有辦法讓數據源發生變化後顯示的數據也隨之發生變化?先來想想ListView是怎麼做的, ListView的數據是通過Adapter提供的,當數據發生改變時,我們通過notifyDatasetChanged通過UI去改變數據,這裏面的原理其實就是內容觀察者,慶幸的是DataBinding也支持內容觀察者,而且使用起來也相當方便!

BaseObservable 
我們可以通過Observable的方式去通知UI數據已經改變了,當然了,官方爲我們提供了更加簡便的方式BaseObservable,我們的實體類只需要繼承該類,稍做幾個操作,就能輕鬆實現數據變化的通知。如何使用呢? 首先我們的實體類要繼承BaseObservale類,第二步在Getter上使用註解@Bindable,第三步,在Setter裏調用方法notifyPropertyChanged,第四步,完成。就是這麼簡單,下面我們來實際操作一下。 
首先定義一個實體類,並繼承BaseObservable

  1. public class Student extends BaseObservable {  
  2.     private String name;  
  3.   
  4.     public Student() {  
  5.     }  
  6.   
  7.     public Student(String name) {  
  8.         this.name = name;  
  9.     }  
  10.   
  11.     @Bindable  
  12.     public String getName() {  
  13.         return name;  
  14.     }  
  15.   
  16.     public void setName(String name) {  
  17.         this.name = name;  
  18.         notifyPropertyChanged(org.loader.app4.BR.name);  
  19.     }  
  20. }  

觀察getName方法,我們使用了@Bindable註解,觀察setName,我們調用了notifyPropertyChanged方法,這個方法還需要一個參數,這裏參數類似於R.java,保存了我們所有變量的引用地址,這裏我們使用了name。 
再來看看佈局文件。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.         <import type="org.loader.app4.Student" />  
  4.         <variable  
  5.             name="stu"  
  6.             type="Student"/>  
  7.         <variable  
  8.             name="click"  
  9.             type="org.loader.app4.MainActivity" />  
  10.     </data>  
  11.     <TextView  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="wrap_content"  
  14.         android:onClick="@{click.click}"  
  15.         android:text="@{stu.name}"/>  
  16. </layout>  

不多說了,我們給TextView設置了文本,還有點擊事件。Activity,

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     private Student mStu;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         org.loader.app4.Custom binding = DataBindingUtil.setContentView(this,  
  9.                 R.layout.activity_main);  
  10.         mStu = new Student("loader");  
  11.         binding.setStu(mStu);  
  12.         binding.setClick(this);  
  13.     }  
  14.   
  15.     public void click(View view) {  
  16.         mStu.setName("qibin");  
  17.     }  
  18. }  

這段代碼,首先顯示的是loader,當我們點擊TextView時,界面換成qibin。

ObservableFields家族 
上面使用BaseObservable已經非常容易了,但是google工程師還不滿足,繼續給我們封裝了一系列的ObservableFields,這裏有ObservableFieldObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable
ObservableFields的使用方法就更加簡單了,例如下面代碼,

  1. public class People {  
  2.     public ObservableField<String> name = new ObservableField<>();  
  3.     public ObservableInt age = new ObservableInt();  
  4.     public ObservableBoolean isMan = new ObservableBoolean();  
  5. }  

很簡單,只有三個ObservableField變量,並且沒有getter和setter,因爲我們不需要getter和setter。 
在xml中怎麼使用呢?

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.   
  4.         <variable  
  5.             name="people"  
  6.             type="org.loader.app4.People" />  
  7.     </data>  
  8.     <LinearLayout  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:orientation="vertical">  
  12.         <TextView  
  13.             android:layout_width="wrap_content"  
  14.             android:layout_height="wrap_content"  
  15.             android:text="@{people.name}"/>  
  16.   
  17.         <TextView  
  18.             android:layout_width="wrap_content"  
  19.             android:layout_height="wrap_content"  
  20.             android:text="@{String.valueOf(people.age)}"/>  
  21.         <TextView  
  22.             android:layout_width="wrap_content"  
  23.             android:layout_height="wrap_content"  
  24.             android:text='@{people.isMan ? "man" : "women"}'/>  
  25.     </LinearLayout>  
  26. </layout>  

也很簡單,直接使用變量,那怎麼賦值和取值呢?這些ObservableField都會有一對getset方法,所以使用起來也很方便了:

  1. ...  
  2. mPeople = new People();  
  3. binding.setPeople(mPeople);  
  4. mPeople.name.set("people");  
  5. mPeople.age.set(19);  
  6. mPeople.isMan.set(true);  
  7. ...  

也不多說了。

Observable Collections 
既然普通的變量我們有了ObservableFields的分裝,那集合呢?當然也有啦,來看着兩個:ObservableArrayMap,ObservableArrayList。使用和普通的Map、List基本相同,直接看代碼:

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.         <variable  
  4.             name="map"  
  5.             type="android.databinding.ObservableArrayMap<String,String>" />  
  6.         <variable  
  7.             name="list"  
  8.             type="android.databinding.ObservableArrayList<String>" />  
  9.     </data>  
  10.     <LinearLayout  
  11.         android:layout_width="wrap_content"  
  12.         android:layout_height="wrap_content"  
  13.         android:orientation="vertical">  
  14.         <TextView  
  15.             android:layout_width="wrap_content"  
  16.             android:layout_height="wrap_content"  
  17.             android:text="@{map[`name`]}"/>  
  18.         <TextView  
  19.             android:layout_width="wrap_content"  
  20.             android:layout_height="wrap_content"  
  21.             android:text="@{list[0]}"/>  
  22.     </LinearLayout>  
  23. </layout>  

在佈局中,使用方式和普通的集合一樣,如果看不太懂,可以往上翻博客,看上面的集合是怎麼使用的。 
在來看java文件,怎麼設置數據,

  1. ObservableArrayMap<String, String> map = new ObservableArrayMap<>();  
  2. ObservableArrayList<String> list = new ObservableArrayList<>();  
  3. map.put("name""loader or qibin");  
  4. list.add("loader!!!");  
  5. binding.setMap(map);  
  6. binding.setList(list);  

太簡單了,簡直和ListMap使用方法一模一樣!!! 

demo源碼下載,戳這裏

十、inflate 
不知道大家注意沒有,上面的代碼我們都是在activity中通過DataBindingUtil.setContentView來加載的佈局的,現在有個問題了,如果我們是在Fragment中使用呢?Fragment沒有setContentView怎麼辦?不要着急,Data Binding也提供了inflate的支持! 
使用方法如下,大家肯定會覺得非常眼熟。

  1. MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);  
  2. MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);  

接下來,我們就嘗試着在Fragment中使用一下Data Binding吧。 
首先還是那個學生類,Student

  1. public class Student extends BaseObservable {  
  2.     private String name;  
  3.     private int age;  
  4.   
  5.     public Student() {  
  6.     }  
  7.   
  8.     public Student(int age, String name) {  
  9.         this.age = age;  
  10.         this.name = name;  
  11.     }  
  12.   
  13.     @Bindable  
  14.     public int getAge() {  
  15.         return age;  
  16.     }  
  17.   
  18.     public void setAge(int age) {  
  19.         this.age = age;  
  20.         notifyPropertyChanged(org.loader.app5.BR.age);  
  21.     }  
  22.   
  23.     @Bindable  
  24.     public String getName() {  
  25.         return name;  
  26.     }  
  27.   
  28.     public void setName(String name) {  
  29.         this.name = name;  
  30.         notifyPropertyChanged(org.loader.app5.BR.name);  
  31.     }  
  32. }  

繼續,activity的佈局

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity">  
  6.   
  7.     <FrameLayout  
  8.         android:id="@+id/container"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"/>  
  11.   
  12. </RelativeLayout>  

activity的代碼,

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         getSupportFragmentManager().beginTransaction()  
  8.                 .replace(R.id.container, new MyFragment()).commit();  
  9.     }  
  10. }  
重點來了,我們這裏data binding的操作都放在了fragment裏,那麼我們先來看看fragment的佈局。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.         <import type="org.loader.app5.Student" />  
  4.         <variable  
  5.             name="stu"  
  6.             type="Student" />  
  7.         <variable  
  8.             name="frag"  
  9.             type="org.loader.app5.MyFragment" />  
  10.     </data>  
  11.   
  12.     <LinearLayout  
  13.         android:orientation="vertical"  
  14.         android:layout_width="match_parent"  
  15.         android:layout_height="wrap_content">  
  16.         <TextView  
  17.             android:layout_width="wrap_content"  
  18.             android:layout_height="wrap_content"  
  19.             android:onClick="@{frag.click}"  
  20.             android:text="@{stu.name}"/>  
  21.   
  22.         <TextView  
  23.             android:layout_width="wrap_content"  
  24.             android:layout_height="wrap_content"  
  25.             android:text="@{String.valueOf(stu.age)}"/>  
  26.     </LinearLayout>  
  27. </layout>  
兩個TextView分別綁定了Student的name和age字段,而且給name添加了一個點擊事件,點擊後會調用Fragment的click方法。我們來迫不及待的看一下Fragment怎麼寫:

  1. public class MyFragment extends Fragment {  
  2.   
  3.     private Student mStu;  
  4.   
  5.     @Nullable  
  6.     @Override  
  7.     public View onCreateView(LayoutInflater inflater,  
  8.                              ViewGroup container, Bundle savedInstanceState) {  
  9.         org.loader.app5.Custom binding = DataBindingUtil.inflate(inflater,  
  10.                 R.layout.frag_layout, container, false);  
  11.         mStu = new Student(20"loader");  
  12.         binding.setStu(mStu);  
  13.         binding.setFrag(this);  
  14.         return binding.getRoot();  
  15.     }  
  16.   
  17.     public void click(View view) {  
  18.         mStu.setName("qibin");  
  19.         mStu.setAge(18);  
  20.     }  
  21. }  

onCreateView中,不同於在Activity中,這裏我們使用了DataBindingUtil.inflate方法,接受4個參數,第一個參數是一個LayoutInflater對象,正好,我們這裏可以使用onCreateView的第一個參數,第二個參數是我們的佈局文件,第三個參數是一個ViewGroup,第四個參數是一個boolean類型的,和在LayoutInflater.inflate一樣,後兩個參數決定了是否想Container中添加我們加載進來的佈局。 
下面的代碼和我們之前寫的並無差別,但是有一點,onCreateView方法需要返回一個View對象,我們從哪獲取呢?ViewDataBinding有一個方法getRoot可以獲取我們加載的佈局,是不是很簡單? 
來看一下效果:

十一、 Data Binding VS RecyclerView 
有了上面的思路,大家是不是也會在ListView和RecyclerView中使用了?我們僅以一個RecyclerView來學習一下。 
首先來看看item的佈局,

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <data>  
  4.         <variable  
  5.             name="stu"  
  6.             type="org.loader.app6.Student" />  
  7.     </data>  
  8.   
  9.     <RelativeLayout  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="match_parent">  
  12.   
  13.         <TextView  
  14.             android:layout_width="wrap_content"  
  15.             android:layout_height="wrap_content"  
  16.             android:text="@{stu.name}"  
  17.             android:layout_alignParentLeft="true"/>  
  18.   
  19.         <TextView  
  20.             android:layout_width="wrap_content"  
  21.             android:layout_height="wrap_content"  
  22.             android:text="@{String.valueOf(stu.age)}"  
  23.             android:layout_alignParentRight="true"/>  
  24.   
  25.     </RelativeLayout>  
  26. </layout>  

可以看到,還是用了那個Student實體,這樣得代碼,相信你也已經看煩了吧。 
那我們來看看activity的。

  1. private RecyclerView mRecyclerView;  
  2. private ArrayList<Student> mData = new ArrayList<Student>() {  
  3.     {  
  4.         for (int i=0;i<10;i++) add(new Student("loader" + i, 18 + i));  
  5.     }  
  6. };  
  7.   
  8. @Override  
  9. protected void onCreate(Bundle savedInstanceState) {  
  10.     super.onCreate(savedInstanceState);  
  11.     setContentView(R.layout.activity_main);  
  12.   
  13.     mRecyclerView = (RecyclerView) findViewById(R.id.recycler);  
  14.     mRecyclerView.setLayoutManager(new LinearLayoutManager(this,  
  15.             LinearLayoutManager.VERTICAL, false));  
  16.     mRecyclerView.setAdapter(new MyAdapter(mData));  
  17. }  

這裏給RecyclerView設置了一個Adapter,相信最主要的代碼就在這個Adapter裏。

  1. private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {  
  2.   
  3.     private ArrayList<Student> mData = new ArrayList<>();  
  4.   
  5.     private MyAdapter(ArrayList<Student> data) {  
  6.         mData.addAll(data);  
  7.     }  
  8.   
  9.     @Override  
  10.     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {  
  11.         ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater  
  12.                 .from(viewGroup.getContext()), R.layout.item, viewGroup, false);  
  13.         ViewHolder holder = new ViewHolder(binding.getRoot());  
  14.         holder.setBinding(binding);  
  15.         return holder;  
  16.     }  
  17.   
  18.     @Override  
  19.     public void onBindViewHolder(ViewHolder viewHolder, int i) {  
  20.         viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));  
  21.         viewHolder.getBinding().executePendingBindings();  
  22.     }  
  23.   
  24.     @Override  
  25.     public int getItemCount() {  
  26.         return mData.size();  
  27.     }  
  28.   
  29.     class ViewHolder extends RecyclerView.ViewHolder {  
  30.   
  31.         private ViewDataBinding binding;  
  32.   
  33.         public ViewHolder(View itemView) {  
  34.             super(itemView);  
  35.         }  
  36.   
  37.         public void setBinding(ViewDataBinding binding) {  
  38.             this.binding = binding;  
  39.         }  
  40.   
  41.         public ViewDataBinding getBinding() {  
  42.             return this.binding;  
  43.         }  
  44.     }  

果然,這個adapter的寫法和我們之前的寫法不太一樣,首先看看ViewHolder,在這個holder裏,我們保存了一個ViewDataBinding對象,並給它提供了GetterSetter方法, 這個ViewDataBinding是幹嘛的?我們稍後去講。繼續看看onCreateViewHolder,在這裏面,我們首先調用DataBindingUtil.inflate方法返回了一個ViewDataBinding的對象,這個ViewDataBinding是個啥?我們以前沒見過啊,這裏告訴大家我們之前返回的那些都是ViewDataBinding的子類!繼續看代碼,我們new了一個holder,參數是肯定是我們的item佈局了,繼續看,接着我們又把binding設置給了holder,最後返回holder。這時候,我們的holder裏就保存了剛剛返回的ViewDataBinding對象,幹嘛用呢?繼續看onBindViewHolder就知道了。
  1. @Override  
  2. public void onBindViewHolder(ViewHolder viewHolder, int i) {  
  3.     viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));  
  4.     viewHolder.getBinding().executePendingBindings();  
  5. }  

只有兩行代碼,但是都是我們沒有見過的,首先第一行,我們以前都是使用類似binding.setStu這樣方法去設置變量,那這個setVariable呢? 爲什麼沒有setStu,這裏要記住,ViewDataBinding是我們之前用的那些binding的父類,只有自動生成的那些子類纔會有setXXX方法,那現在我們需要在ViewDataBinding中設置變量咋辦?這個類爲我們提供了setVariable去設置變量,第一個參數是我們的變量名的引用,第二個是我們要設置的值。第二行代碼,executePendingBindings的作用是幹嘛的?官方的回答是:
當數據改變時,binding會在下一幀去改變數據,如果我們需要立即改變,就去調用executePendingBindings方法。

所以這裏的作用就是去讓數據的改變立即執行。 
ok,現在看起來,我們的代碼更加簡潔了,而且不需要保存控件的實例,是不是很爽? 來看看效果:

十二、 View with ID 
在使用Data Binding的過程中,我們發現並沒有保存View的實例,但是現在我們有需求需要這個View的實例咋辦?難道走老路findViewById?當然不是啦,當我們需要某個view的實例時,我們只要給該view一個id,然後Data Binding框架就會給我們自動生成該view的實例,放哪了?當然是ViewDataBinding裏面。 
上代碼:

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.         <variable  
  4.             name="str"  
  5.             type="android.databinding.ObservableField<String>" />  
  6.         <variable  
  7.             name="handler"  
  8.             type="org.loader.app7.MainActivity" />  
  9.     </data>  
  10.   
  11.     <TextView  
  12.         android:id="@+id/textView"  
  13.         android:layout_width="match_parent"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="@{str.get}"  
  16.         android:onClick="@{handler.click}"/>  
  17. </layout>  

xml中代碼沒有什麼好說的,都是之前的代碼,如果在這有點迷糊,建議你還是回頭看看上篇博客。需要注意的是, 
我們給TextView了一個id-textView。 
activity,

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     private org.loader.app7.Custom mBinding;  
  4.     private ObservableField<String> mString;  
  5.   
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         mBinding = DataBindingUtil.setContentView(this,  
  10.                 R.layout.activity_main);  
  11.         mString = new ObservableField<String>();  
  12.         mString.set("loader");  
  13.         mBinding.setStr(mString);  
  14.         mBinding.setHandler(this);  
  15.     }  
  16.   
  17.     public void click(View view) {  
  18.         mString.set("qibin");  
  19.         mBinding.textView.setTextColor(Color.GREEN);  
  20.     }  
  21. }  

通過ViewDataBinding類的實例直接去獲取的。

只要我們給了view一個id,那麼框架就會在ViewDataBinding中自動幫我們保存這個view的實例,變量名就是我們設置的id。

十三、 自定義setter 
想想這樣的一種情景,一個ImageView需要通過網絡去加載圖片,那我們怎麼辦?看似好像使用DataBinding不行,恩,我們上面所學到東西確實不能夠解決這個問題,但是DataBinding框架給我們提供了很好的擴展,允許我們自定義setter,那該怎麼做呢?這裏就要引出另一個知識點——BindingAdapter,這是一個註解,參數是一個數組,數組中存放的是我們自定義的’屬性’。接下來就以一個例子學習一下BindingAdapter的使用。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:app="http://schemas.android.com/apk/res-auto">  
  3.     <data class=".Custom">  
  4.         <variable  
  5.             name="imageUrl"  
  6.             type="String" />  
  7.     </data>  
  8.   
  9.     <ImageView  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         app:image="@{imageUrl}"/>  
  13. </layout>  

這裏我們增加了一個命名空間app,並且注意ImageView的app:image屬性,這裏和我們自定義view時自定義的屬性一樣,但是這裏並不需要我們去重寫ImageView,這條屬性的值是我們上面定義的String類型的imageUrl,從名稱中看到這裏我們可能會塞給他一個url。 
activity,

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         org.loader.app8.Custom binding = DataBindingUtil.setContentView(this,  
  7.                 R.layout.activity_main);  
  8.         binding.setImageUrl("http://images.csdn.net/20150810/Blog-Image%E5%89%AF%E6%9C%AC.jpg");  
  9.     }  
  10. }  

果然在這裏我們set了一個url,那圖片怎麼加載呢?這裏就要使用到我們剛纔說的BindingAdapter註解了。

  1. public class Utils {  
  2.     @BindingAdapter({"bind:image"})  
  3.     public static void imageLoader(ImageView imageView, String url) {  
  4.         ImageLoaderUtils.getInstance().displayImage(url, imageView);  
  5.     }  
  6. }  

我們定義了一個Utils類,這個類你可以隨便起名,該類中只有一個靜態的方法imageLoader,該方法有兩個參數,一個是需要設置數據的view, 
一個是我們需要的url。值得注意的是那個BindingAdapter註解,看看他的參數,是一個數組,內容只有一個bind:image,僅僅幾行代碼,我們不需要 
手工調用Utils.imageLoader,也不需要知道imageLoader方法定義到哪了,一個網絡圖片加載就搞定了,是不是很神奇,這裏面起關鍵作用的就是BindingAdapter 
註解了,來看看它的參數怎麼定義的吧,難道是亂寫?當然不是,這裏要遵循一定的規則,

以bind:開頭,接着書寫你在控件中使用的自定義屬性名稱。

這裏就是image了,不信來看。

  1. <ImageView  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="wrap_content"  
  4.     app:image="@{imageUrl}"/>  

看看運行結果:

十四、 Converters 
Converter是什麼呢?舉個例子吧:假如你的控件需要一個格式化好的時間,但是你只有一個Date類型額變量咋辦?肯定有人會說這個簡單,轉化完成後在設置,恩,這也是一種辦法,但是DataBinding還給我們提供了另外一種方式,雖然原理一樣,但是這種方式使用的場景更多,那就是——Converter。和上面的BindingAdapter使用方法一樣,這也是一個註解。下面還是以一段代碼的形式進行學習。

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data class=".Custom">  
  3.         <variable  
  4.             name="time"  
  5.             type="java.util.Date" />  
  6.     </data>  
  7.   
  8.     <TextView  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="@{time}"/>  
  12. </layout>  

看TextView的text屬性,我們需要一個String類型的值,但是這裏確給了一個Date類型的,這就需要我們去定義Converter去轉換它, 
activity,

  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         org.loader.app9.Custom binding = DataBindingUtil.setContentView(this,  
  7.                 R.layout.activity_main);  
  8.         binding.setTime(new Date());  
  9.     }  
  10. }  

去給這個Date類型的變量設置值。怎麼去定義Converter呢? 看代碼:

  1. public class Utils {  
  2.   
  3.     @BindingConversion  
  4.     public static String convertDate(Date date) {  
  5.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");  
  6.         return sdf.format(date);  
  7.     }  
  8. }  

和上面一樣,我們不需要關心這個convertDate在哪個類中,重要的是他的@BindingConversion註解,這個方法接受一個Date類型的變量,正好我們的android:text設置的就是一個Date類型的值,在方法內部我們將這個Date類型的變量轉換成String類型的日期並且返回。這樣UI上就顯示出我們轉化好的字符串。 
看看效果:

好了,到這裏DataBinding的知識我們就算學習完了,在學完之後發現這東西也沒什麼難度,學會使用就ok了,而且android官網也有非常詳細的文檔, 
這兩篇博客只是系統的去講解了DataBinding的使用,大家在以後使用的過程中發現忘記怎麼用了,可以再來翻看博客或者直接去官方查看。 
ok, 那就到這裏吧,下次見。

參考鏈接:https://developer.android.com/tools/data-binding/guide.html

博客源碼下載:代碼下載,戳這裏

轉自:http://blog.csdn.net/qibin0506/article/details/47393725

發佈了44 篇原創文章 · 獲贊 175 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章