android引入MVVM框架時間還不長,目前還很少有應用到app中的。但它是比較新的技術,使用它來搭建項目能省很多代碼,而且能使用代碼架構比較清晰。本篇文章是我在學習MVVM時翻譯的,篇幅比較長,先翻譯前半部分。
這篇文檔解析如何使用數據綁定庫來寫響應式布局并減少用來綁定應用程序和布局之間冗余代碼,使用邏輯層和布局分離。
數據綁定庫提供了即靈活又全面的兼容性——它的支持庫.so可以用在android2.1平臺(API?level?7+)。
使用MVVM需要Gradle1.5.0-alphal或更高版本的插件。
### 一、測試版
請注意,數據綁定庫是一個測試版。雖然數據綁定是處于測試階段,開發人員應該注意以下事項:
*目前它只是一個測試版,可能不適合你的用例,我們需要你的反饋。
*數據綁定庫測試版有重大的改變,包括那些沒有源代碼與應用程序不兼容,也就是說,可能以后需要進行更改。
*開發人員應該隨時發布應用程序構建與數據綁定庫測試版,它與Android?SDK和谷歌的服務條款適用,建議經常采用新庫或工具來徹底測試自己的應用程序。
### 二、搭建環境
首先你需要在Android?SDK?manager中下載支持庫。
配置您的應用程序,在你的module中的build.gradle文件中添加dataBinding元素。
使用下面的代碼片段來配置數據綁定:
~~~
android {
? ? ....
? ? dataBinding {
? ? ? ? enabled = true
? ? }
}
~~~
如果你的app使用到的庫使用到數據綁定,那你的app也需要在build.gradle文件中進行配置。
另外,確保您正在使用一個兼容的版本的Android工作室。Android?Studio?1.3或更新的版本支持數據綁定。
### 數據綁定的布局文件
### 編寫你的第一個數據綁定布局
數據綁定布局文件略有不同,它以layout作為布局的起點,,后跟一個data標簽和一個view元素。這個view元素是普通不使用數據綁定布局的根元素。一個示例文件是這樣的:
~~~
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView 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>
~~~
data中使用的variable表示了一個可能會在這個布局中作用的屬性。
~~~
<variable name="user" type="com.example.User"/>
~~~
布局屬性的設置使用“@?{?}”語法,這里TextView的文字屬性就設置為user中的firstName屬性。
~~~
<TextView android:layout_width="wrap_content"
? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? android:text="@{user.firstName}"/>
~~~
#
### 對象
讓我們假設現在有一個User的普通java對象(POJO):
~~~
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
~~~
這種類型的對象數據不會改變。通常在應用程序的數據讀取一次,永遠不會改變。它還可以改為javabean對象:
~~~
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;
}
}
~~~
從數據綁定的角度來看,這兩個類是等價的。用于設置TextView的android:文本的表達式@{user.firstName}將訪問第一個類中的firstName字段和后一個類的getFirstName()方法。另外,如果firstName()方法存在,它還將訪問firstName()方法。
### 綁定數據
默認情況下,綁定類的名稱是基于布局文件的名稱起的,它是將布局文件名開頭大寫并加上“Binding”而成。上述布局文件名稱為main_activity.xml,那它的綁定類名為MainActivityBinding。這個類擁有所有從屬性(例如用戶變量)到布局的綁定關系并知道如何賦值綁定表達式。最簡單的方法創建綁定的方法就是通過反射:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
~~~
完成了!運行應用程序,你會看到在UI上看到“Test?User”。另外,你也可以通過以下代碼來獲取view:
~~~
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
~~~
如果你想在?ListView或者RecyclerView?使用數據綁定,你應該這樣使用:
~~~
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
~~~
綁定事件
事件可以直接綁定到處理程序方法,類似于android:onClick可以分配給一個Activity的方法。事件屬性名稱是listener方法的名稱有一部分。例如,[View.OnLongClickListener](http://developer.android.com/reference/android/view/View.OnLongClickListener.html)有一個onLongClick()方法,所以這個事件的屬性名應寫為android:onLongClick。
分配一個事件給handler,使用方法名稱來作為正常綁定表達式的變量,例如,如果您的數據對象有兩個方法:
~~~
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}
~~~
綁定表達式會為View?分配一個click監聽事件。
~~~
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
</LinearLayout>
</layout>
~~~
### 三、布局細節
### Imports
零個或多個導入元素可以使用內部數據元素。這些允許簡單引用類內部布局文件,就像在Java。
~~~
<data>
<import type="android.view.View"/>
</data>
~~~
現在,view可以使用在你的綁定表達式:
~~~
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
~~~
當有類名稱沖突,其中一個類可能用alias進行重命名:
~~~
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
~~~
現在,Vista可以使用?com.example.real.estate.view中的引用并且View可用于代表android.view.view。導入類型可以在變量和表達式中作為類型引用:
~~~
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
~~~
注意:Android studio還沒處理好import的自動導入,因此你的IDE可能無法自動導入變量,你可以在變量定義使用完全限定名稱,這樣你的應用程序仍可以正常編譯。
~~~
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
~~~
導入類型時也可以使用在引用靜態字段和方法中:
~~~
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
~~~
就像在java中,java.lang.*會自動導入一樣。
### 變量
數據元素可以使用任意數量的變量元素。每個變量元素描述一個屬性,它都必須在layout中通過綁定表達式來設置值。
~~~
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
~~~
變量在編譯時進行類型檢查,如果一個變量實現了Observable接口或者它是一個observable集合,那應該反射得到。如果變量是一個沒有實現Observable的基類或接口,,那變量將無法被觀察到!
當不同的布局文件中的變量配置不相同(如landscape或者protrait),這些變量將被組合起來。在這些布局文件之間定義變量不會有沖突。
生成的綁定類中,對應每個變量都有一個setter和getter方法。變量將設置為默認的Java值直到setter被調用,如引用類型默認為null,int默認值是0,boolean默認為false等。
### Custom?Binding?Class?Names
默認情況下,生成綁定類名稱是基于布局文件的名稱的,把布局文件名稱開頭用大寫,刪除下劃線(_),最后加上”Binding”。這個類將被放置在module的一個databinding包模塊下。例如,contact_item.xml將產生一個類名ContactItemBinding的類。如果module包名為com.example.my。那么它將會被放置在com.example.my.app.databinding這個包下。
通過調整數據元素的class屬性可以將綁定類重命名或放置在不同的包。例如:
~~~
<data class="ContactItem">
...
</data>
~~~
這個生成綁定類ContactItem將會存在module包下的databinding包中。如果想讓生成的類存放在module中的另一個包下,它加入前綴”.”:
~~~
<data class=".ContactItem">
...
</data>
~~~
這種情況下,ContactItem將被直接放置在module包下,如果想放在其它包可以使用包名的全稱:
~~~
<data class="com.example.ContactItem">
...
</data>
~~~
### Includes
變量可以通過一個included元素導入到布局文件中,included中要有應用程序名稱空間和屬性的變量名:
~~~
?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
? ? ? ? xmlns:bind="http://schemas.android.com/apk/res-auto">
? ?<data>
? ? ? ?<variable name="user" type="com.example.User"/>
? ?</data>
? ?<LinearLayout
? ? ? ?android:orientation="vertical"
? ? ? ?android:layout_width="match_parent"
? ? ? ?android:layout_height="match_parent">
? ? ? ?<include layout="@layout/name"
? ? ? ? ? ?bind:user="@{user}"/>
? ? ? ?<include layout="@layout/contact"
? ? ? ? ? ?bind:user="@{user}"/>
? ?</LinearLayout>
</layout>
~~~
### 在這里,name.xml和contact.xml中都必須要有user變量。
數據綁定不支持直接在merge元素里添加include并以此來作為子view,
~~~
<pre name="code" class="html"><span style="font-weight: normal;"><?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout></span>
~~~
### 表達式語法
### 共同特性
表達式語法跟java語法很像,下面的一樣的語法:
·?數學計算?+?-?/?*?%
·?字符串連接?+
·?邏輯運算符&&?||
·?位運算&?|?^
·?一元運算?+?-?!?~
·?位移>>?>>>?<<
·?比較==?>?<?>=?<=
·?instanceof
·?Grouping?()
·?文字?-?character,?String,?numeric,?null
·?Cast
·?方法調用
·?字段訪問
·?數組訪問?[?]
·?三元運算符?:
例如:
~~~
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
~~~
### 不具備的方法
有一些操作只能在Java表達式中使用。
·?this
·?super
·?new
·?Explicit?generic?invocation
#### 空聯合操作
聯合操作符(??)不為空的話選擇左邊操作,為空的話選擇右邊操作。
~~~
android:text="@{user.displayName ?? user.lastName}"
~~~
它等同于:
~~~
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
~~~
#### 避免指針異常
生成數據綁定代碼會自動檢查null,避免空指針異常。例如,在表達式@?{?user.name?},如果用戶是null,user.name分配其默認值(null)。如果是@?{?user.age?}。age是int,那么它將默認值為0。
#### Collections
集合共同點:數組,list,sparse?list和map,為了方便訪問,均可以使用[?]操作符。
~~~
<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]}"
~~~
### 文字字符串
使用單引號包裹住屬性值,這樣就很容易在表達式中使用雙引號:
~~~
android:text='@{map["firstName"]}'
~~~
還可以使用雙引號包圍的屬性值。當這樣做時,字符串應該使用"或引號(`)。
~~~
android:text="@{map[`firstName`}"
android:text="@{map["firstName"]}"
~~~
### 資源文件
可以使用正常的訪問資源的表達式語法:?
~~~
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
~~~
格式字符串和復數可以通過提供參數來定義:
~~~
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
~~~
當一個復數多個參數,所有參數都應該通過:
~~~
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
~~~
一些資源需要顯式類型的評估:.?
<table class=" "><tbody><tr><td valign="top" style="background:rgb(153,153,153)"><p><span style="color:rgb(255,255,255)">Type</span></p></td><td valign="top" style="background:rgb(153,153,153)"><p><span style="color:rgb(255,255,255)">Normal?Reference</span></p></td><td valign="top" style="background:rgb(153,153,153)"><p><span style="color:rgb(255,255,255)">Expression?Reference</span></p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>String[]</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@array</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@stringArray</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>int[]</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@array</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@intArray</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>TypedArray</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@array</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@typedArray</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>Animator</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@animator</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@animator</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>StateListAnimator</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@animator</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@stateListAnimator</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>color?<span style="color:rgb(0,102,0)">int</span></p></td><td valign="top" style="background:rgb(247,247,247)"><p>@color</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@color</p></td></tr><tr><td valign="top" style="background:rgb(247,247,247)"><p>ColorStateList</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@color</p></td><td valign="top" style="background:rgb(247,247,247)"><p>@colorStateList</p></td></tr></tbody></table>
- 前言
- Android底部tab與標題欄相結合
- Android免費獲取短信驗證碼
- android中Handler的源碼分析
- 詳解Fragment的傳值問題
- 詳談gson解析
- android新控件之toolbar,floatingActionButton,SnackBar,CollapsingToolbarLayout
- android自定義控件
- 淺談android的線程池
- Android的消息處理機制,AsyncTask源碼解析
- IPC——android進程間通信
- CoCos2d_android入門所需知道的一切
- Cocos2d_android你所需要知道的一切(下)
- Activity你需要知道的一切
- Activity啟動過程源碼分析
- Data Binding Guide——google官方文檔翻譯(上)
- Data Binding Guide——google官方文檔翻譯(下)
- android TextView實現跑馬燈效果
- android中生成excel
- Volley源碼解析
- LayoutInflater源碼解析
- android發送郵件
- android測試工具MonkeyRunner--google官網翻譯
- android View繪制源碼分析
- Android中Window添加View的底層原理
- 仿美團商品選購下拉菜單實現