引子
上圖中有一些 TextView 和 Button 等,正常情況下,互聯網APP都會從服務器抓取數值,然後在 Activity中 findViewById 再進行setText等等。這篇文章就是用來解放你的雙手勞動力 的,使用數據綁定庫可以不用去findView不用在寫繁瑣的 setText,只要從服務器獲取json 轉換成 javaBean格式然後 set,duang,,,,, 所有的值就自己展現在該有的地方了。
Demo: https://github.com/Afra55/DataBindingApplication
我自己認爲,先看Demo,然後帶着疑問 去閱讀,會有一種解惑的情懷。>~<
原文:https://developer.android.com/intl/zh-cn/tools/data-binding/guide.html#build_environment
本文介紹瞭如何使用數據綁定庫
數據綁定庫提供了靈活性和廣泛的兼容性-這是一個支持庫,所以你可以在所有的android平臺上使用它。這個庫需要Android Plugin for Gradle 1.5.0-alpha1或更高版本。
構建環境
在 Android SDK中 下載支持庫。
在 build.gradle 文件中添加 dataBinding 元素。
android {
....
dataBinding {
enabled = true
}
}
此外,確保使用的是 Android Studio,
Data Binding Layout Files
寫你的第一個數據綁定表達式
數據綁定佈局和普通的佈局稍有不同,有一個標籤爲 layout 的根佈局,其次是元素 data 和一個視圖根元素。例子:
<?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>
variable 的寫法如下, type 的值指定完整路徑的 java 類:
<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 對象:
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
這種類型的對象有一個永遠不會改變的數據。這是應用中是常見的,以具有被寫入一次數據並隨後從不改變。另外,也可以使用一個JavaBeans對象:
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:text
屬性中使用的表達式 @{user.firstName}
會訪問前面那個 User 類的 fristName 字段,或者後面那個 User 類的 getFirstName()
方法。或者,如果firstName()
方法存在,它也將被解析爲的 firstName()
。
綁定數據
默認情況下,綁定類將基於佈局文件的名稱來產生,上面的佈局文件的名字是 main_activity.xml
通常情況下生成的類是 MainActivityBinding
, 這個類包含所有從佈局屬性到佈局視圖的綁定 (e.g. the user variable),並且知道如何分配綁定表達式的值。下面是一個最簡單的綁定的例子:
@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 上看到 user 對象的數據了。另外,還可以這樣獲取視圖:
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 分配一個方法。事件的屬性名是由監聽的方法分配的,例如View.OnLongClickListener
有一個方法onLongClick()
,因此這個事件的屬性是 android:onLongClick
。
分配一個事件處理機制,就是在表達式中調用這個方法名。例如,如果你的數據對象有兩個方法:
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}
下面的例子就是通過表達式給 view 分配了個點擊監聽的事件:
<?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>
一些特殊的點擊事件需要其他的屬性來避免與 android:onClick 的衝突,已經創建了下面的屬性,以避免這樣的衝突:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
佈局細節
Imports
import
元素允許你的佈局文件引用類,就像在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}"/>
當你想引入自己寫的一個 View 類的時候,你可以使用“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。引用類型也可能在變量的type裏使用:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List"/>
</data>
<TextView
android:text="@{((User)(userList.get(0))).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
import類型可能在要引用靜態方法的時候使用:
<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.* ,自動導入。
Variablesb(變量)
可以在 data 元素中使用任意的 variable 元素。一個 variable 元素描述了一個可能在佈局文件中的綁定表達式中使用的屬性。
<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>
變量的 type 是在編譯時檢查,因此如果一個變量實現了 Observable 或者是一個 Observable 集合,應在 type 中進行反映。如果變量是一個沒有實現 Observable* 接口的基類或者接口,變量將不會被發現。
當有不同配置(如橫向或縱向)的不同佈局文件,變量將被合併。有這些佈局文件之間一定不能有相互衝突的變量定義。
生成的綁定類會爲每一個描述的變量生成一個 getter 和 setter 方法。在 setter 被調用之前,變量將採取默認的 java 值——null(引用類型),0(int),false(布爾類型)等。
一個叫 context 的特殊變量會在綁定表達式中需要的時候生成,這是 context 變量的 value 值是 Context (從根視圖的 getContext 方法中得到的)。這個 context 變量會被同名的 顯示聲明 變量覆蓋掉。
自定義綁定類的名字
默認情況下,綁定類的名字是基於佈局文件的名字生成的。將第一個字母大寫,去掉下劃線將接下來的字母接上並大寫第一個字母,最後跟上“Binding”。例如,佈局文件 contact_item.xml
會生成 ContactItemBinding
,如果這個模塊的包名是 com.example.my.app
,那麼生成的綁定類會放置在 com.example.my.app.databinding
。
綁定類可以通過改變 data 元素下的 class
屬性來重命名或者放置到其他包裏。例如:
<data class="ContactItem">
...
</data>
這樣綁定類就被重命名爲 ContactItem
。如果綁定類要在模塊包下的其他package下生成,則要加前綴 “.”
。例如:
<data class=".ContactItem">
...
</data>
這種情況下,綁定類會放到 模塊包.ContactItem
下即 com.example.my.app.ContactItem
包下。如果要完全自定義包名,則提供完整路徑即可:
<data class="com.example.ContactItem">
...
</data>
Includes
變量可能會被傳入到引用的佈局中,這麼做:
<?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>
included 的佈局必須要有 user 變量,例如:
<?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>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.sex}"/>
</layout>
綁定數據不支持 merge 元素的佈局引用,以下佈局是不支持的:
<?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>
表達式語言
公共特性
這些表達式看起來很像java表達式,下面這些都是一樣的:
- 數學計算
+ - / *%
- 連接字符串
+
- 邏輯運算
&& ||
- 二進制運算
& | ^
- 單目運算
+ - ! ~
- 移位運算
>> >>> <<
- 比較運算
== > < >= <=
instanceof
- 分組
()
- 常量
character, String, numeric, null
- 造型運算符(Cast)
- 方法調用
- 字段訪問
- 訪問數組
[]
- 三元運算
?:
例如:
ndroid:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
以下操作不可用
- this
- super
- new
- Explicit generic invocation
空合併運算
空合併運算符(??
),如果左面的不爲 null 則選擇左邊的,否則選擇右邊。
android:text="@{user.displayName ?? user.lastName}"
它等價於下面的公式:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
引用屬性
android:text="@{user.lastName}"
#### 避免 NullPointerException
生成的數據綁定代碼會自動檢查空值和避免空指針異常。例如表達式@{user.name}
,如果 user 是 null, user.name
就會被指定默認值 (null)。如果引用 user.age
,由於 age 是 int 類型,所以指定默認值爲 0。
集合
常見的集合:arrays, lists, sparse lists, 和 maps,可能使用 []
操作符來進行訪問。
<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="MapM<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"]}"
Resources
可以使用普通的訪問資源的表達式,如下事例:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
當格式化 string 或者 plurals 需要提供參數:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
當 plurals 有多個參數時,每個參數都要傳入:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
下面一些資源的顯示調用跟普通的調用略有不同:
Data Objects
當綁定了 java 對象後,再對他進行修改並不會讓 UI 更新,這裏有三個不同的數據通知機制:
當它們中的一個觀察到綁定在 UI 上的數據對象被修改時,UI 就會自動更新。
Observable Objects
一個類實現 Observable
接口允許綁定對象綁定一個 listener 來監聽對象所有的 屬性變化。
爲了讓開發變得簡單,有個名爲 BaseObservable
的基類用來實現 listener 的註冊機制,實現了這個基類的數據類會一直負責通知屬性的變化,通知的方式是通過指定 getter 方法 Bindable
註解和在 setter 方法的通知來實現的。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
在編譯的時候註解 Bindable
會在 BR 類中生成一個屬性的入口,以便在 setter 方法中通知。BR 類會在模塊包下生成。
ObservableFields
Observale 類的創建會有一小部分的準備工作,因此開發者想要節約時間或者只監聽少部分的屬性,這時可以使用 ObservableField 和它的兄弟們 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable。ObservableFields 是一個獨立的自給自足的 ObservableFields 對象,擁有一個獨立的字段。在訪問原始數據時避免了 boxing 和 unboxing 操作。如下事例,在數據類中創建 public final 字段:
public class Son {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
下面是訪問和設置值的例子:
son.firstName.set("Child");
int age = son.age.get();
Observable 集合
一些應用使用更多的動態結構來保存數據。Observable 集合允許使用 key-value 的形式來保存數據。 ObservableArrayMap
在當 key 是個引用類型(比如 string)的時候非常有用。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在 佈局中, 使用 string key 來訪問數據:
<data>
<import type="android.databinding.ObservableArrayMap"/>
<variable name="user" type="ObservableArrayMap"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
ObservableArrayList
在key 是 數字的時候使用,和 ArrayList 類似:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在佈局中,通過索引來訪問數據:
<data>
<import type="android.databinding.ObservableArrayList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableArrayList"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
生成綁定
生成的綁定類用來鏈接變量和佈局中的視圖。上文已經說過了,綁定類的名字和存儲的位置都是可以自定義的。綁定類都會繼承一個父類 ViewDataBinding。
創建
在佈局中的視圖和表達式綁定之後會很快生成綁定類。這裏有幾種綁定佈局的方法,其中最常見的是使用綁定類中的靜態方法,只需調用 inflate 方法這一步即可填充佈局和綁定佈局:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果只綁定佈局,可以使用下面的方法:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時候綁定不能被提前知道,在這種情況下可以使用 DataBindingUtil 類(該類的一些方法與文中有不同,具體看源碼):
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
Views With IDs
佈局中每一個視圖的 ID 都會生成一個 public final 字段。綁定機制會在視圖層次提取視圖的 ID,這種方式來獲取視圖要比調用 findViewById 快。例如:
<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}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>
生成的綁定類會有下面的字段:
public final TextView firstName;
public final TextView lastName;
然後可以直接使用:
mBinding.lastName.setText("Afra55");
變量
每一個變量都會有一個方法去訪問它。
<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>
會在綁定類中生成相應的 setter 和 getter 方法:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
ViewStubs
ViewStubs 默認是不顯示的,加載時纔會被其他佈局替換而顯示出來。
ViewStub 本質上是不會出現在視圖層次的, 由於 Views 是final類型,所以 ViewStub綁定後就會轉換爲 ViewStubProxy對象,讓開發着可以在 ViewStub 存在並被填充時訪問 ViewStub。
當填充另一個佈局時,新佈局的綁定也要被建立。因此, ViewStubProxy 一定要監聽 ViewStub 的 ViewStub.OnInflateListener
接口 和及時建立綁定。因此當綁定的建立完成時 ViewStubProxy 允許開發者設置 OnInflateListener 來回調。
舉例:
佈局中添加 StubView:
<ViewStub
android:id="@+id/viewstub"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout="@layout/test_viewstub" />
test_viewstub.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.databinding.databindingapplication.User"/>
</data>
<TextView
android:orientation="vertical"
android:text='@{user.lastName + " StubViewProxy"}'
android:gravity="center"
android:textColor="@android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</layout>
在代碼中引用:
// 設置佈局加載監聽用來綁定數據,綁定後 viewStub 轉換成 ViewStubProxy
mBinding.viewstub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
TestViewstubBinding viewDataBinding = DataBindingUtil.bind(inflated);
User user = new User("xx", "gg", true);
viewDataBinding.setUser(user);
}
});
// 顯示佈局,Andoird studio會報紅,可能還麼支持,直接運行即可。
if (!mBinding.viewstub.isInflated()) {
mBinding.viewstub.getViewStub().inflate();
}
Advanced Binding
動態變量
有時,一些特殊的綁定類不會被知道。例如,一個 RecyclerView.Adapter 裏的操作不允許任何佈局知道綁定類。但是依舊會在 onBindViewHolder(VH, int).
裏分配綁定值。
在這個例子裏,所有RecyclerView 佈局的綁定都會有一個 “item” 變量。BindingHolder
自己實現一個 getBinding
方法用來獲取 ViewDataBinding。
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
這個例子需要去實現 RecyclerView.Adapter 。我自己實現了個例子:
主佈局:
<android.support.v7.widget.RecyclerView
android:id="@+id/recylerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Item 佈局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.databinding.databindingapplication.User" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:text="@{user.sex}" />
</FrameLayout>
</layout>
ViewHolder:
/**
* Created by yangshuai in the 21:23 of 2016.06.01 .
*/
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
private LayoutSexBinding mBinding;
public RecyclerViewHolder(View itemView) {
super(itemView);
mBinding = LayoutSexBinding.bind(itemView);
}
public ViewDataBinding getBinding() {
return mBinding;
}
}
Adapter:
/**
* Created by yangshuai in the 21:21 of 2016.06.01 .
*/
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewHolder> {
private Context mContext;
private List<User> mUsers = new ArrayList<>();
public RecyclerViewAdapter(Context context, List<User> users) {
mContext = context;
mUsers.addAll(users);
}
public void updata(List<User> users) {
mUsers.addAll(users);
notifyDataSetChanged();
}
public void clear() {
mUsers.clear();
notifyDataSetChanged();
}
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_sex, parent, false);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
holder.getBinding().setVariable(BR.user, mUsers.get(position));
holder.getBinding().executePendingBindings();
}
@Override
public int getItemCount() {
return mUsers.size();
}
}
代碼配置:
private void initRecyclerAdapter() {
List<User> users = new ArrayList<>();
users.add(new User("unKnow"));
users.add(new User("man"));
users.add(new User("woman"));
users.add(new User("woman"));
users.add(new User("woman"));
users.add(new User("woman"));
users.add(new User("woman"));
users.add(new User("woman"));
mRecyclerViewAdapter = new RecyclerViewAdapter(this, users);
mBinding.recylerview.setLayoutManager(new LinearLayoutManager(this));
mBinding.recylerview.setItemAnimator(new DefaultItemAnimator());
mBinding.recylerview.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
mBinding.recylerview.setAdapter(mRecyclerViewAdapter);
}
效果圖:
立即綁定
當綁定數據有變化時,視圖的對應變化會有一段時間,所以當綁定要立刻強制執行時,使用 executePendingBindings()
方法即可。
後臺線程
可以在後臺線程中改變一個數據模型(非集合)。數據綁定會本地化 變量/字段 用來避免併發問題。
參數的 Setter
不論什麼時候只要被綁定的值改變了,生成的綁定類都會調用 setter 方法來執行表達式並將結果展現在視圖上。數據綁定框架有幾種方式來自定義方法去設置值。
自動 Setter
在佈局文件中與綁定表達式對應的參數,比如 TextView 的 android:text 的 值是表達式得到的結果,則如果表達式返回的是 String 類型,則回去檢索 setText(String) 方法,如果表達式返回的是 int 類型,則數據綁定會去檢索 setText(int) 方法。所以一定要當心表達式所返回的結果類型。可以藉助這種特性,來實現自定義的 setter。比如:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
DrawerLayout 沒有 scrimColor 和 drawerListener 兩個參數,這兩個參數與數據綁定相關聯,則他們會自動去調用 方法 setScrimColor 和 setDrawerListener
方法去傳遞表達式的結果。
可以在自定義 view 中使用:
<com.example.databinding.databindingapplication.view.MTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@android:color/white"
app:aaaa='@{user.lastName + "AAAAA"}'
app:bbbb="@{@color/colorPrimary}"/>
app:aaaa 和 app:bbbb 是自定義的方法:
/**
* Created by yangshuai in the 20:18 of 2016.06.02 .
*/
public class MTextView extends TextView {
public MTextView(Context context) {
super(context);
}
public MTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setBbbb(int color) {
setBackgroundColor(color);
}
public void setAaaa(String text) {
setText(text);
}
}
效果如下:
重命名 setter
我們可以使用 註解 @bindingMethods 來重命名參數的 setter 方法,例如官方的這個例子 android:tint 它鏈接的方法是 setImageTintList(ColorStateList) 而不是 setTint:
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
不推薦這麼做。
自定義 Setter
一些參數需要自定義綁定邏輯。例如,沒有 setter 方法 與參數 android:paddingLeft 鏈接,只有 setPadding(left, top, right, bottom) 存在。
註解 @BindingAdapter 可以實現該參數被調用時,鏈接自定義方法:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
也可以有多個參數的自定義情況:
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>
這個綁定適配器loadImage會在 參數 imageUrl 是 string 類型和 error 是drawable 類型時被調用,並傳入這兩個參數的值。
綁定適配器會在 handler 持有老數據,因此方法可以 得到老數據和新數據進行比較,老數據的位置在新數據的前面:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
還有事件的監聽獲取只允許一個 Listener 來監聽:
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
當一個 接口 有多個抽象方法時,一定要把他們分離,舉例:
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因爲在佈局表達式中改變其一就可能影響到另一個,所以上面這種情況有兩個方法的接口,要有三個綁定適配器,兩個參數各有一個方法,其次還有個整體改變的方法:
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
android.databinding.adapters.ListenerUtil 類幫助跟蹤獲取之前的 Listenner。
轉換
Object 轉換
當綁定表達式返回一個 Object 時,Object 會根據 鏈接的 setter 方法的輸入參數來轉換自己的類型。
例如:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
userMap 返回了一個 Object,這個Object 會根據檢索到的方法 setText(CharSequene)自動轉變類型爲 CharSequene。 但有時候還需要我們自己在表達式中進行轉換,看情況嘍。
自定義轉換
有些時候需要在特殊類型之間進行轉換,例如 background,下面的寫法是錯誤的:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
background 參數的值應該是 Drawable,但是傳入的 color 是 integer類型。 int 類型的值應該被轉換成 ColorDrawable。
例如:
<com.example.databinding.databindingapplication.view.MTextView
android:layout_width="match_parent"
android:layout_height="20dp"
android:gravity="center"
android:background="@{@color/colorAccent}"
/>
普通情況下是錯誤的,但是在 MTextView中加上靜態方法:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
background 參數的值檢查到是 int 類型時就會調用 convertColorToDrawable 方法自動轉換 int 型到 ColorDrawable 從而運行成功。
下面這種情況是不允許的, 不允許混合類型:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Android Studio 對 Data Binding 的支持
- 語法高亮
- 表達式語法錯誤時高亮錯誤
- XML 代碼自動補全
- 可以通過引用快速導航
數組和泛型,就像 Observable類一樣,可能在沒有錯誤的時候顯示錯誤,不用擔心直接運行即可。
可以使用下面的方法在綁定表達式沒有返回有效值時來顯示默認數據:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName, default = ss}" />
這個 TextView 就會默認顯示 ss 字符串。
後話
這篇文章前前後後寫了有一個多月,對原文的一邊翻譯一邊也有了自己的見解,我覺得數據綁定庫是個很好的方向,希望 Google 能繼續完善,比如代碼的自動補全,錯誤提示等。
生活之於點點滴滴,積累以得源泉,祝你健康。