天天看點

Android官方資料綁定架構DataBinding

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

今天來了解一下android最新給我們帶來的資料綁定架構——data binding library。資料綁定架構給我們帶來了更大的友善性,以前我們可能需要在<code>activity</code>裡寫很多的<code>findviewbyid</code>,煩人的代碼也增加了我們代碼的耦合性,現在我們馬上就可以抛棄那麼多的<code>findviewbyid</code>。說到這裡,有人可能會有個疑問:我使用一些注解架構也可以不用<code>findviewbyid</code>啊,是的,但是注解注定要拖慢我們代碼的速度,data

binding則不會,官網文檔說還會提高解析xml的速度,最主要的data binding并不是單單減少了我們的<code>findviewbyid</code>,更多好處請往下看文章。

一、環境 

在開始使用新東西之前,我們需要稍微的配置一下環境,這裡要求你的android studio版本是1.3+,使用eclipse的同學暫時還沒有辦法使用該架構,請換用android studio。還有,在開始之前,請更新你的<code>support repository</code>到最新的版本。 

萬事俱備,那我們就開始搭配環境!

建立一個<code>project</code>,在<code>dependencies</code>中添加以下依賴

建立<code>module</code>,并且在<code>module</code>的build.gradle檔案中添加

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

二、data binding嘗試 

在代碼開始,我們并不直接進入新東西的講解,而且以一段代碼展現data binding的魅力。 

首先我們需要一個<code>java bean</code>,很簡單,一個學生類。

再來看看我們布局檔案怎麼寫:

可以看到我們的xml布局和以前還有有一定的差别的,但是差别也不是很大。 

最後來看看<code>activity</code>怎麼寫。

<code>activity</code>的代碼非常簡單,就添加了兩行代碼,而且,值得注意的是:我們并沒有<code>findviewbyid</code>然後再去<code>settext</code>。 

這段小代碼運作的結果大家可能已經猜到了,就是在界面上顯示<code>loader</code>和<code>山東萊蕪</code>兩句話。

Android官方資料綁定架構DataBinding

)

在看完小執行個體後,大家是不是感覺棒棒哒? 沒有了之前的find控件,沒有了settext,<code>activity</code>代碼更加簡潔明了! 

下面開始,我們進入data binding的學習!

三、 初始data binding 

上面的代碼算是帶領我們進入了data binding的世界,那我們先從布局檔案開始入手data binding吧。再來看看上面的布局檔案。

我們的根節點變成了<code>layout</code>,在<code>layout</code>的子節點中分成兩部分,第一部分是<code>data</code>節點,第二部分才是我們之前的根節點,在<code>data</code>節點下我們又定義了一個<code>variable</code>, 

從名稱上看,這應該是一個變量,變量的名稱是<code>stu</code>,類型是<code>org.loader.androiddatabinding.student</code>,這類似我們在java檔案中這麼定義:

ok,這樣很好了解了吧,不過這裡要寫<code>student</code>完整的包名,一個還好,如果這裡我們需要多個<code>student</code>呢?要累死?

no,no,no,我們還可以向寫java檔案那樣導入包。

這樣寫,就類似于java的

import

org.loader.app2.student;...student stu;...

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

恩,注意看兩個<code>textview</code>的<code>android:text</code>,它的值是一個以<code>@</code>開始,以{}包裹的形式出現,而内容呢?是<code>stu.name</code>。stu就是我們上面定義的<code>variable</code>, 

name還記得嗎?是我們<code>student</code>類中的一個變量。其實這裡就會去調用<code>stu.getname()</code>方法。 

好了,很快,我們就入門了data binding,下面讓我們來多定義幾個變量試試看。

來看看定義的變量,多了好幾個,有一個<code>string</code>類型的變量我們并沒有導包,這裡說明一下,和在java裡一樣,<code>java.lang</code>包裡的類,我們是可以不用導包的,再往下,一個<code>boolean</code>和<code>int</code>類型的變量,都是java基本類型的,是以說嘛,在這裡定義變量,你就想成是在java裡定義就ok。 

再來看看這幾個<code>textview</code>,第二個,我們直接使用<code>@{str}</code>來為<code>android:text</code>設定成上面定義個<code>str</code>的值,繼續往下要注意了,我們使用了

android:text="@{string.valueof(num)}"

來設定了一個<code>int</code>類型的變量,大家都知道我們在給<code>android:text</code>設定<code>int</code>類型的值時一定要轉化為<code>string</code>類型,要不它就認為是資源檔案了,這裡我們還學到了一點,在xml中,我們不僅可以使用變量,而且還可以調用方法!

四、 變量定義的進階部分 

在上面,我們學會了如何去在xml中定義變量,但是不知道你發現沒?我們沒有定義像<code>list</code>、<code>map</code>等這樣的集合變量。那到底能不能定義呢?答案肯定是可以的,而且定義的方式和我們上面的基本一緻,差別就在于我們還需要為它定義key的變量,例如:

這段代碼比較長,但是我們僅關心那幾個集合和數組,可以看到我們定義集合和定義普通變量一樣,隻不過這裡我們還指定了一些的泛型,例如:<code>arraylist&amp;lt;string&gt;</code>。 

下面我們還為下面使用這些集合準備了幾個key,也都是變量。 

繼續看看怎麼使用,和我們在java中使用不同,這裡都是以:集合變量名[key]的形式使用,如果你的key是一個字面字元串可以使用反引号,也可以使用轉義後的雙引号。恩,這裡也沒有什麼可以說的了,大家多看幾遍就掌握了,都是概念性的東西,記住就ok。

五、在java代碼中使用 

前面定義了這麼多變量,但是我們還沒有給他們指派!在哪指派呢?肯定是在java代碼中使用了,大部分情況我們還是在<code>activity</code>中去使用它,以前我們都是在<code>oncreate</code>方法中通過<code>setcontentview</code>去設定布局,但現在不一樣了,現在我們是用過<code>databindingutil</code>類的一個靜态方法<code>setcontentview</code>設定布局,同時該方法會傳回一個對象,什麼對象?這個對象有點特殊,它是一個自動生成的類的對象,看下面:

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

将我們布局檔案的首字母大寫,并且去掉下劃線,将下劃線後面的字母大寫,加上binding組成。

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

傳回的,databindingutil.setcontentview的兩個參數分别是目前<code>activity</code>和布局檔案。那接下來,就是我們關心的給變量指派了。

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

六、 表達式 

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

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

簡單解釋一下,如果str是null,text的值就是str本身,否則就是”not null”。 

它還支援一下表達式:

mathematical + - / * %

string concatenation +

logical &amp;&amp; ||

binary &amp; | ^

unary + - ! ~

shift &gt;&gt; &gt;&gt;&gt; &lt;&lt;

comparison == &gt; &lt; &gt;= &lt;=

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

自定義binding名稱 

還記得系統為我們生成好的那個binding類名嗎?如果隻能使用那樣的是不是有點太暴力了?好在google對我們還算友好了,允許我們自定義binding名稱,定制名稱也很簡單,就是給data一個class字段就ok。 

例如:

那麼:databindingutils.setcontentview傳回的binding類就是:<code>你的應用包名.custom</code>。

八、事件綁定 

大家都知道,在xml中我們可以給<code>button</code>設定一個<code>onclick</code>來達到事件的綁定,現在databinding也提供了事件綁定,而且不僅僅是<code>button</code>。 

來看一下:

定義了一個<code>eventhandlers</code>類型的<code>handlers</code>變量,并在onclick的時候執行<code>eventhandlers</code>的<code>handleclick</code>方法。 

繼續看看eventhandlers是怎麼寫的。

很簡單,就是簡單的<code>toast</code>了一下,這裡要注意的是,<code>handlerclick</code>方法需要一個<code>view</code>的參數。

九、 資料對象 

我們學會了通過binding為我們的變量設定資料,但是不知道你有沒有發現一個問題,當我們資料改變的時候會怎樣?資料是跟随着改變呢?還是原來的資料呢?這裡告訴你答案:很不幸,顯示的還是原來的資料?那有沒有辦法讓資料源發生變化後顯示的資料也随之發生變化?先來想想<code>listview</code>是怎麼做的, <code>listview</code>的資料是通過<code>adapter</code>提供的,當資料發生改變時,我們通過<code>notifydatasetchanged</code>通過ui去改變資料,這裡面的原理其實就是内容觀察者,慶幸的是databinding也支援内容觀察者,而且使用起來也相當友善!

baseobservable 

我們可以通過observable的方式去通知ui資料已經改變了,當然了,官方為我們提供了更加簡便的方式<code>baseobservable</code>,我們的實體類隻需要繼承該類,稍做幾個操作,就能輕松實作資料變化的通知。如何使用呢?

首先我們的實體類要繼承<code>baseobservale</code>類,第二步在<code>getter</code>上使用注解<code>@bindable</code>,第三步,在<code>setter</code>裡調用方法<code>notifypropertychanged</code>,第四步,完成。就是這麼簡單,下面我們來實際操作一下。 

首先定義一個實體類,并繼承<code>baseobservable</code>

觀察getname方法,我們使用了<code>@bindable</code>注解,觀察setname,我們調用了<code>notifypropertychanged</code>方法,這個方法還需要一個參數,這裡參數類似于<code>r.java</code>,儲存了我們所有變量的引用位址,這裡我們使用了name。 

再來看看布局檔案。

不多說了,我們給<code>textview</code>設定了文本,還有點選事件。activity,

這段代碼,首先顯示的是loader,當我們點選<code>textview</code>時,界面換成qibin。

observablefields家族 

上面使用<code>baseobservable</code>已經非常容易了,但是google工程師還不滿足,繼續給我們封裝了一系列的<code>observablefields</code>,這裡有<code>observablefield</code>,<code>observableboolean</code>,<code>observablebyte</code>,<code>observablechar</code>,<code>observableshort</code>,<code>observableint</code>,<code>observablelong</code>,<code>observablefloat</code>,<code>observabledouble</code>,<code>observableparcelable</code>

observablefields的使用方法就更加簡單了,例如下面代碼,

很簡單,隻有三個observablefield變量,并且沒有getter和setter,因為我們不需要getter和setter。 

在xml中怎麼使用呢?

也很簡單,直接使用變量,那怎麼指派和取值呢?這些observablefield都會有一對<code>get</code>和<code>set</code>方法,是以使用起來也很友善了:

也不多說了。

observable collections 

既然普通的變量我們有了observablefields的分裝,那集合呢?當然也有啦,來看着兩個:<code>observablearraymap</code>,<code>observablearraylist</code>。使用和普通的map、list基本相同,直接看代碼:

在布局中,使用方式和普通的集合一樣,如果看不太懂,可以往上翻部落格,看上面的集合是怎麼使用的。 

在來看java檔案,怎麼設定資料,

太簡單了,簡直和<code>list</code>、<code>map</code>使用方法一模一樣!!! 

demo源碼下載下傳,戳這裡

十、inflate 

不知道大家注意沒有,上面的代碼我們都是在activity中通過<code>databindingutil.setcontentview</code>來加載的布局的,現在有個問題了,如果我們是在<code>fragment</code>中使用呢?<code>fragment</code>沒有<code>setcontentview</code>怎麼辦?不要着急,data

binding也提供了<code>inflate</code>的支援! 

使用方法如下,大家肯定會覺得非常眼熟。

接下來,我們就嘗試着在<code>fragment</code>中使用一下data

binding吧。 

首先還是那個學生類,<code>student</code>

<code></code>

繼續,activity的布局

activity的代碼,

重點來了,我們這裡data binding的操作都放在了fragment裡,那麼我們先來看看fragment的布局。

兩個textview分别綁定了student的name和age字段,而且給name添加了一個點選事件,點選後會調用fragment的click方法。我們來迫不及待的看一下fragment怎麼寫:

在<code>oncreateview</code>中,不同于在activity中,這裡我們使用了databindingutil.inflate方法,接受4個參數,第一個參數是一個layoutinflater對象,正好,我們這裡可以使用oncreateview的第一個參數,第二個參數是我們的布局檔案,第三個參數是一個viewgroup,第四個參數是一個boolean類型的,和在<code>layoutinflater.inflate</code>一樣,後兩個參數決定了是否想<code>container</code>中添加我們加載進來的布局。 

下面的代碼和我們之前寫的并無差别,但是有一點,<code>oncreateview</code>方法需要傳回一個view對象,我們從哪擷取呢?<code>viewdatabinding</code>有一個方法<code>getroot</code>可以擷取我們加載的布局,是不是很簡單? 

來看一下效果:

Android官方資料綁定架構DataBinding

十一、 data binding vs recyclerview 

有了上面的思路,大家是不是也會在listview和recyclerview中使用了?我們僅以一個recyclerview來學習一下。 

首先來看看item的布局,

可以看到,還是用了那個student實體,這樣得代碼,相信你也已經看煩了吧。 

那我們來看看activity的。

這裡給<code>recyclerview</code>設定了一個adapter,相信最主要的代碼就在這個adapter裡。

果然,這個adapter的寫法和我們之前的寫法不太一樣,首先看看viewholder,在這個holder裡,我們儲存了一個<code>viewdatabinding</code>對象,并給它提供了<code>getter</code>和<code>setter</code>方法,

這個<code>viewdatabinding</code>是幹嘛的?我們稍後去講。繼續看看<code>oncreateviewholder</code>,在這裡面,我們首先調用<code>databindingutil.inflate</code>方法傳回了一個<code>viewdatabinding</code>的對象,這個<code>viewdatabinding</code>是個啥?我們以前沒見過啊,這裡告訴大家我們之前傳回的那些都是<code>viewdatabinding</code>的子類!繼續看代碼,我們new了一個holder,參數是肯定是我們的item布局了,繼續看,接着我們又把binding設定給了holder,最後傳回holder。這時候,我們的holder裡就儲存了剛剛傳回的<code>viewdatabinding</code>對象,幹嘛用呢?繼續看<code>onbindviewholder</code>就知道了。

隻有兩行代碼,但是都是我們沒有見過的,首先第一行,我們以前都是使用類似<code>binding.setstu</code>這樣方法去設定變量,那這個<code>setvariable</code>呢?

為什麼沒有<code>setstu</code>,這裡要記住,<code>viewdatabinding</code>是我們之前用的那些binding的父類,隻有自動生成的那些子類才會有<code>setxxx</code>方法,那現在我們需要在<code>viewdatabinding</code>中設定變量咋辦?這個類為我們提供了<code>setvariable</code>去設定變量,第一個參數是我們的變量名的引用,第二個是我們要設定的值。第二行代碼,<code>executependingbindings</code>的作用是幹嘛的?官方的回答是:

當資料改變時,binding會在下一幀去改變資料,如果我們需要立即改變,就去調用<code>executependingbindings</code>方法。

是以這裡的作用就是去讓資料的改變立即執行。 

ok,現在看起來,我們的代碼更加簡潔了,而且不需要儲存控件的執行個體,是不是很爽? 來看看效果:

Android官方資料綁定架構DataBinding

十二、 view with id 

在使用data binding的過程中,我們發現并沒有儲存view的執行個體,但是現在我們有需求需要這個view的執行個體咋辦?難道走老路<code>findviewbyid</code>?當然不是啦,當我們需要某個view的執行個體時,我們隻要給該view一個id,然後data

binding架構就會給我們自動生成該view的執行個體,放哪了?當然是<code>viewdatabinding</code>裡面。 

上代碼:

xml中代碼沒有什麼好說的,都是之前的代碼,如果在這有點迷糊,建議你還是回頭看看上篇部落格。需要注意的是, 

我們給<code>textview</code>了一個id-<code>textview</code>。 

activity,

通過<code>viewdatabinding</code>類的執行個體直接去擷取的。

隻要我們給了view一個id,那麼架構就會在viewdatabinding中自動幫我們儲存這個view的執行個體,變量名就是我們設定的id。

十三、 自定義setter 

想想這樣的一種情景,一個<code>imageview</code>需要通過網絡去加載圖檔,那我們怎麼辦?看似好像使用databinding不行,恩,我們上面所學到東西确實不能夠解決這個問題,但是databinding架構給我們提供了很好的擴充,允許我們自定義setter,那該怎麼做呢?這裡就要引出另一個知識點——<code>bindingadapter</code>,這是一個注解,參數是一個數組,數組中存放的是我們自定義的’屬性’。接下來就以一個例子學習一下<code>bindingadapter</code>的使用。

這裡我們增加了一個命名空間<code>app</code>,并且注意imageview的<code>app:image</code>屬性,這裡和我們自定義view時自定義的屬性一樣,但是這裡并不需要我們去重寫imageview,這條屬性的值是我們上面定義的string類型的imageurl,從名稱中看到這裡我們可能會塞給他一個url。 

activity,

果然在這裡我們set了一個url,那圖檔怎麼加載呢?這裡就要使用到我們剛才說的bindingadapter注解了。

我們定義了一個<code>utils</code>類,這個類你可以随便起名,該類中隻有一個靜态的方法imageloader,該方法有兩個參數,一個是需要設定資料的view, 

一個是我們需要的url。值得注意的是那個<code>bindingadapter</code>注解,看看他的參數,是一個數組,内容隻有一個<code>bind:image</code>,僅僅幾行代碼,我們不需要 

手工調用utils.imageloader,也不需要知道imageloader方法定義到哪了,一個網絡圖檔加載就搞定了,是不是很神奇,這裡面起關鍵作用的就是<code>bindingadapter</code> 

注解了,來看看它的參數怎麼定義的吧,難道是亂寫?當然不是,這裡要遵循一定的規則,

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

這裡就是<code>image</code>了,不信來看。

看看運作結果:

Android官方資料綁定架構DataBinding

十四、 converters 

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

看textview的text屬性,我們需要一個string類型的值,但是這裡确給了一個date類型的,這就需要我們去定義converter去轉換它, 

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

和上面一樣,我們不需要關心這個convertdate在哪個類中,重要的是他的<code>@bindingconversion</code>注解,這個方法接受一個date類型的變量,正好我們的android:text設定的就是一個date類型的值,在方法内部我們将這個date類型的變量轉換成string類型的日期并且傳回。這樣ui上就顯示出我們轉化好的字元串。 

看看效果:

Android官方資料綁定架構DataBinding

好了,到這裡databinding的知識我們就算學習完了,在學完之後發現這東西也沒什麼難度,學會使用就ok了,而且android官網也有非常詳細的文檔, 

這兩篇部落格隻是系統的去講解了databinding的使用,大家在以後使用的過程中發現忘記怎麼用了,可以再來翻看部落格或者直接去官方檢視。 

ok, 那就到這裡吧,下次見。

參考連結:https://developer.android.com/tools/data-binding/guide.html

部落格源碼下載下傳:代碼下載下傳,戳這裡