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有一個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]}"
文字字元串
使用單引號包裹住屬性值,這樣就非常easy在表達式中使用雙引號:
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)}"
一些資源須要顯式類型的評估:.
Type | Normal Reference | Expression Reference |
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |