谷歌推荐Data Binding实现MVVM模式(完整文档)
Data Binding 類庫
這篇文檔將教你如何運用 Data Binding 類庫來編寫聲明試布局,并且盡量減少粘合代碼對你的應(yīng)用邏輯和布局上的綁定。
Data Binding 是一種靈活和廣泛兼容的類庫,它是一個支持庫,因此你可以在任何 Android 2.1(API level 7+) 以上的設(shè)備 使用。
為了使用 Data Binding,Android Gradle 插件版本必須為 1.5.0-alpha1 或以上,查看 如何升級你的 Gradle 插件。
贈送源碼:GitHub - Pangu-Immortal/MagicWX: 🔥免root實現(xiàn) Android改機(一鍵新機)技術(shù)解密,微信無限多開等。。
《最完整的Android逆向知識體系》
構(gòu)建環(huán)境
為了獲取 Data Binding,去 Android SDK manager 下載 它的支持庫。
在你的應(yīng)用 module 的 build.gradle 添加 dataBinding 來讓你的應(yīng)用支持 Data Binding。
用以下代碼片段來配置 Data Binding:
若你有一個應(yīng)用 module 用了一個依賴了 Data Binding 的類庫,也一樣要在該 module 中配置開啟 Data Binding。
另外,如果想使用 Data Binding,你們你的 Android Studio 版本必須等于或大于 1.3。
Data Binding 布局文件
編寫你的第一個 Data Binding 表達式
Data Binding 的布局文件有一點不一樣,它以 layout 標(biāo)簽作為根標(biāo)簽,并且有一個data 元素和 一個 view 元素作為子標(biāo)簽,這個 view 元素就是你沒有使用 Data Binding 時該有的布局文件。以下是一個例子:
<?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><LinearLayoutandroid: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 標(biāo)簽下的 variable 是你在這個 Data Binding 布局文件中有可能使用到的對象。
<variable name="user" type="com.example.User"/>布局中使用 @{} 語法來包裹 variable 中的對象屬性,在下面例子中,TextView 的 text 屬性的值用 user 的 firstName 屬性來替代。
<TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}"/>數(shù)據(jù)對象
現(xiàn)在讓我們假設(shè)你有一個普通的 Java 對象(POJO)User:
public class User {public final String firstName;public final String lastName;public User(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;} }這個對象屬性(final 修飾)是不可變的,如果你的數(shù)據(jù)對象只提供只讀權(quán)限并且之后不會再去修改的話,這種做法很普遍。我們也可以用 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;} }從數(shù)據(jù)綁定的角度來看,這兩個類是等價的。TextView 的 android:text 屬性值會通過表達式 @{user.firstName} 來獲取第一個類中的 fistName 字段值,活著獲取第二個類中的 getFirstName() 方法返回的值。另外,如果 firstName() 方法存在的話也是可以獲取到值的。
綁定數(shù)據(jù)
默認(rèn)情況下,將根據(jù)布局文件的名稱生成一個綁定類,將其轉(zhuǎn)換為 Pascal 格式并將 Binding 作為其后綴。上面的布局文件是名稱
main_activity.xml ,因此生成的綁定類是 MainActivityBinding。這個類將布局屬性(例如用戶變量)綁定到布局的視圖中,并知道如何通過表達式來賦值。創(chuàng)建綁定類的最簡單方式是在視圖 inflate 的時候:
完成了!運行這個應(yīng)用,你會在界面中看到測試的 User。另外,你可以通過一些方
式獲取綁定類:
如果你在 ListView 或者 RecyclerView 中使用數(shù)據(jù)綁定電話,你可以通過一些方式獲取綁定類:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); //or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);事件處理
數(shù)據(jù)綁定允許你編寫表達式來處理從視圖中分派的事件(例如 onClick)。除少數(shù)例外,事件屬性名稱由偵聽器中的方法名稱來確定。例如,View.OnLongClickListener 有一個 onLongClick()方法,所以這個事件的屬性是 android:onLongClick。有以下兩種方式來處理一個事件。
- 方法引用:在表達式中,可以引用符合偵聽器方法簽名的方法。 當(dāng)表達式被評估為方法引用時,數(shù)據(jù)綁定將方法引用和所有者對象包裝在偵聽器中,并將該偵聽器設(shè)置在目標(biāo)視圖上。 如果表達式被評估為 null,則數(shù)據(jù)綁定不會創(chuàng)建偵聽器,而是設(shè)置一個空的偵聽器。
- 監(jiān)聽器綁定:當(dāng)事件發(fā)生時,lambda 表達式將被評估。 數(shù)據(jù)綁定總是會在視圖上創(chuàng)建一個監(jiān)聽器。 當(dāng)事件被發(fā)送時,監(jiān)聽器將評估 lambda 表達式。
方法引用
事件可以直接綁定到處理的方法中,類似于 android:onClick 可以作為 Activity 的一個方法一樣。與 View#onClick 屬性相比,一個主要的優(yōu)點是表達式在編譯時被處理,因此如果方法不存在或者它的簽名不正確,就會收到編譯時錯誤。
方法引用和監(jiān)聽器綁定的主要區(qū)別在于實際的監(jiān)聽器實現(xiàn)是在綁定數(shù)據(jù)時創(chuàng)建的,而不是在事件觸發(fā)時創(chuàng)建的。
要將事件分配給其處理程序,請使用常規(guī)綁定表達式,其值是要調(diào)用的方法名稱。 例如,如果你的數(shù)據(jù)對象有兩個方法:
public class MyHandlers {public void onClickFriend(View view) { ... } }綁定表達式可以為 View 分配一個點擊監(jiān)聽器:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="handlers" type="com.example.MyHandlers"/><variable name="user" type="com.example.User"/></data><LinearLayoutandroid: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="@{handlers::onClickFriend}"/></LinearLayout> </layout>請注意,表達式中方法的簽名必須與監(jiān)聽器對象中方法的簽名完全匹配。
監(jiān)聽器綁定
監(jiān)聽器綁定是事件發(fā)生時運行的綁定表達式。類似于方法引用,但是允許你運行任意的數(shù)據(jù)綁定表達式。 此功能適用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。
在方法引用中,方法的參數(shù)必須與事件偵聽器的參數(shù)匹配。 在監(jiān)聽器綁定中,只有你的返回值必須與監(jiān)聽器的期望返回值相匹配(除非它返回值為 void )。 例如,您可以有一個具有以下方法的 Presenter 類:
public class Presenter {public void onSaveClick(Task task){} }然后你可以綁定你的點擊事件到你的類中,例如:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="task" type="com.android.example.Task" /><variable name="presenter" type="com.android.example.Presenter" /></data><LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"><Button android:layout_width="wrap_content" android:layout_height="wrap_content"android:onClick="@{() -> presenter.onSaveClick(task)}" /></LinearLayout></layout>監(jiān)聽器僅可以允許用 lambda 表達式作為根元素。 當(dāng)表達式中有回調(diào)時,數(shù)據(jù)綁定會自動為事件創(chuàng)建必要的偵聽器和注冊表。 當(dāng)視圖觸發(fā)事件時,數(shù)據(jù)綁定將評估給定的表達式。 就像在常規(guī)的綁定表達式一樣,當(dāng)這些監(jiān)聽器表達式被評估的時候,你仍然可以獲取數(shù)據(jù)綁定的空值和保證線程安全。
請注意,在上面的例子中,我們沒有定義傳入 onClick(android.view.View) 的視圖參數(shù)。 監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩個選擇:您可以忽略該方法的所有參數(shù)或?qū)⑵淙棵?如果您想要命名參數(shù),則可以在表達式中使用它們。 例如,上面的表達式可以寫成:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"或者如果你想使用表達式中的參數(shù),可以像下面這樣:
public class Presenter {public void onSaveClick(View view, Task task){} } android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"你可以在 lambda 表達式中使用多個參數(shù):
public class Presenter {public void onCompletedChanged(Task task, boolean completed){} } <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />如果正在偵聽的事件返回值不是 void,則表達式必須返回相同類型的值。 例如,如果你想監(jiān)聽長按事件,你的表達式應(yīng)該返回布爾值。
public class Presenter {public boolean onLongClick(View view, Task task){} } android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"如果由于空對象而無法評估表達式,Data Binding 將返回該類型的默認(rèn) Java 值。 例如,引用類型為 null,int 為 0,boolean 為false 等等。
如果您需要使用帶謂詞的表達式(例如三元),則可以使用 void 作為符號。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"避免復(fù)雜的監(jiān)聽器
監(jiān)聽器表達式非常強大,可以讓你的代碼變得非常容易閱讀。 另一方面,包含復(fù)雜表達式的監(jiān)聽器也會使您的布局難以閱讀和維護。這些表達式應(yīng)該像從 UI 中傳遞可用數(shù)據(jù)到回調(diào)方法一樣簡單。你應(yīng)該從偵聽器表達式調(diào)用的回調(diào)方法內(nèi)實現(xiàn)業(yè)務(wù)邏輯。
存在一些專門的單擊事件處理程序,它需要除 android:onClick 之外的其他屬性以避免沖突。 已創(chuàng)建了以下屬性以避免這種沖突:
| SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
| ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
| ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
布局文件細節(jié)
Imports
數(shù)據(jù)元素內(nèi)可以使用零個或多個 import 元素。 這些就像在 Java 中一樣可以輕松地引用類到你的布局文件中。
<data><import type="android.view.View"/> </data>現(xiàn)在 View 類可以在你的綁定表達式中使用了。
<TextViewandroid:text="@{user.lastName}"android:layout_width="wrap_content"android:layout_height="wrap_content"android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>如果類名有沖突的話,其中一個類則需起別名了。
<import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/>現(xiàn)在,在布局文件中,Vista 被當(dāng)作 com.example.real.estate.View 引入,View 被當(dāng)作 android.view.View 引入。 導(dǎo)入的類型可以用作變量和表達式中的類型引用:
<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 尚未處理導(dǎo)入,因此自動導(dǎo)入變量在你的的 IDE 中可能無法完成。 你的應(yīng)用程序仍然可以正常編譯,你可以通過在變量定義中使用完全限定的名稱來解決 IDE 的這個問題。
<TextViewandroid:text="@{((User)(user.connection)).lastName}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>當(dāng)在表達式中引用靜態(tài)字段和方法時,也可以使用導(dǎo)入的類型:
<data><import type="com.example.MyStringUtils"/><variable name="user" type="com.example.User"/> </data> … <TextViewandroid:text="@{MyStringUtils.capitalize(user.lastName)}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>就像在 Java 文件中一樣,java.lang.* 會被自動導(dǎo)入。
Variables
data 元素內(nèi)可以使用任意的 variable。 每個變量表示可以在布局中設(shè)置的屬性,以用于布局文件中的綁定表達式。
<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>變量類型在編譯時被檢查,所以如果一個變量實現(xiàn)了 Observable 或者一個 observable collection,那么它應(yīng)該被反映在類型中。 如果變量是沒有實 Observable 接口的基類或接口,那么它將不會被觀察!
當(dāng)不同的配置(例如橫向或縱向)有不同的布局文件時,變量將被合并。 這些布局文件之間不得有沖突的變量定義。
生成的綁定類將為每個描述的變量設(shè)置一個 setter 和 getter 方法。 變量將采用默認(rèn)的 Java 值,直到調(diào)用 setter 為止 。對于引用類型為 null,對于 int 為0,對于 boolean 為 false 等。
自定義綁定類的名字
默認(rèn)情況下,根據(jù)布局文件的名稱生成一個綁定類,以大寫字母開頭,刪除下劃線(_)并之后的單詞首字母大寫,然后添加后綴 Binding。 這個類將被放置在模塊包下的數(shù)據(jù)綁定包中。 例如,布局文件 contact_item.xml 將生成 ContactItemBinding。 如果模塊包是 com.example.my.app,那么它將被放置在 com.example.my.app.databinding 中。
綁定類可以通過調(diào)整 data 元素的 class 屬性來重命名或放置在不同的包中。 例如:
<data class="ContactItem">... </data>這會在模塊包中的數(shù)據(jù)綁定包中生成綁定類 ContactItem。 如果該類應(yīng)該在模塊包中的其他包中生成,則可以用“.”作為前綴:
<data class=".ContactItem">... </data>在這種情況下,直接在模塊包中生成了 ContactItem。 如果提供完整的包,則可以使用任意的包:
<data class="com.example.ContactItem">... </data>Includes
通過在屬性中使用應(yīng)用程序命名空間和變量名稱,變量可以從包含的布局中傳遞到包含的布局的綁定中:
<?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><LinearLayoutandroid: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 變量。
數(shù)據(jù)綁定不支持 include 作為 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 表達式。 這些是一樣的:
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
例如:
缺少的操作
你在 Java 中使用的一些表達式語法并不支持綁定操作。
- this
- super
- new
- 明確的泛型調(diào)用
空的合并運算符
空合并運算符 ?? 會選擇左邊的運算結(jié)果(如果它不是 null 的話)或右邊的運算結(jié)果(如果它是 null 的話)。
android:text="@{user.displayName ?? user.lastName}"這在功能上等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"屬性引用
當(dāng)一個表達式引用一個類的屬性時,它對字段,setter 和 ObservableFields 使用相同的格式。
android:text="@{user.lastName}"避免空指針異常
生成的數(shù)據(jù)綁定代碼會自動檢查空值并避免空指針異常。 例如,在表達式 @ {user.name} 中,如果 user 為 null,則 user.name 將被分配其默認(rèn)值(null)。 如果引用 user.age,其中age是一個 int,那么它將默認(rèn)為0。
集合
通用的集合:數(shù)組,列表,SparseArray ,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"]}'也可以使用雙引號來包圍屬性值。 這樣做時,字符串文字應(yīng)該使用單引號 ' 或者反引號(`)。
android:text="@{map[`firstName`}" android:text="@{map['firstName']}"資源
使用正常的語法可以將資源作為表達式的一部分進行訪問:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"格式字符串和復(fù)數(shù)可以通過提供參數(shù)來評估:
android:text="@{@string/nameFormat(firstName, lastName)}" android:text="@{@plurals/banana(bananaCount)}"當(dāng)一個復(fù)數(shù)有多個參數(shù)時,所有參數(shù)都應(yīng)該傳遞:
Have an orangeHave %d orangesandroid:text="@{@plurals/orange(orangeCount, orangeCount)}"一些資源需要明確的類型評估:
| String[] | @array | @stringArray |
| int[] | @array | @intArray |
| TypedArray | @array | @typedArray |
| Animator | @animator | @animator |
| StateListAnimator | @animator | @stateListAnimator |
| color int | @color | @color |
| ColorStateList | @color | @colorStateList |
Data Objects
任何普通的舊 Java 對象(POJO)都可以用于數(shù)據(jù)綁定,但修改 POJO 不會導(dǎo)致 UI
更新。 數(shù)據(jù)綁定的真正威力在于通過給你的數(shù)據(jù)對象在數(shù)據(jù)改變時提供通知。 有三種不同的數(shù)據(jù)更改通知機制,Observable objects, observable fields, observable collections.
當(dāng)這些可觀察的數(shù)據(jù)對象被綁定到 UI,并且數(shù)據(jù)對象的屬性改變時,UI 將被自動更新。
Observable Objects
實現(xiàn) Observable 接口的類將允許綁定單個偵聽器附加到綁定對象,以偵聽該對象上所有屬性的更改。
Observable 接口具有添加和刪除偵聽器的功能,但通知是由開發(fā)者決定的。 為了簡化開發(fā),創(chuàng)建了基類 BaseObservable,以實現(xiàn)偵聽器注冊機制。 數(shù)據(jù)類實現(xiàn)者仍然負(fù)責(zé)通知屬性的更改。 這是通過給 getter 分配一個 Bindable 注解并通知 setter 來完成的。
private static class User extends BaseObservable {private String firstName;private String lastName;@Bindablepublic String getFirstName() {return this.firstName;}@Bindablepublic 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 類中生成一個條目。 BR 類文件將在模塊包中生成。 如果數(shù)據(jù)類的基類沒有改變,Observable 接口可以使用方便的 PropertyChangeRegistry 來實現(xiàn),以有效地存儲和通知監(jiān)聽器。
ObservableFields
創(chuàng)建 Observable 類需要做一點工作,所以想要節(jié)省時間或擁有很少屬性的開發(fā)人員可以使用 ObservableField 及其同胞 ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDoubl 和 ObservableParcelable。 ObservableFields 是具有單個字段的獨立的可觀察對象。 原始版本在訪問操作期間避免裝箱和取消裝箱。 要使用,請在數(shù)據(jù)類中創(chuàng)建一個公共 final 字段:
private static class User {public final ObservableField<String> firstName =new ObservableField<>();public final ObservableField<String> lastName =new ObservableField<>();public final ObservableInt age = new ObservableInt(); }就是這樣!要訪問該值,請使用 set 和 get 方法訪問:
user.firstName.set("Google"); int age = user.age.get();Observable Collections
一些應(yīng)用程序使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù),觀察集合允許對這些數(shù)據(jù)對象進行鍵值訪問。當(dāng)鍵是引用類型(如 String)時,ObservableArrayMap 非常有用。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);在布局文件中,map 通過字符串鍵來訪問:
<data><import type="android.databinding.ObservableMap"/><variable name="user" type="ObservableMap<String, Object>"/> </data> … <TextViewandroid:text='@{user["lastName"]}'android:layout_width="wrap_content"android:layout_height="wrap_content"/> <TextViewandroid:text='@{String.valueOf(1 + (Integer)user["age"])}'android:layout_width="wrap_content"android:layout_height="wrap_content"/>當(dāng)鍵是整形是,可以使用 ObservableArrayList:
ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);在布局中,列表可以通過索引來訪問:
<data><import type="android.databinding.ObservableList"/><import type="com.example.my.app.Fields"/><variable name="user" type="ObservableList<Object>"/> </data> … <TextViewandroid:text='@{user[Fields.LAST_NAME]}'android:layout_width="wrap_content"android:layout_height="wrap_content"/> <TextViewandroid:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'android:layout_width="wrap_content"android:layout_height="wrap_content"/>生成綁定類
生成的綁定類將布局變量與布局中的視圖鏈接起來。 如前所述,綁定的名稱和包可能是自定義的。 生成的綁定類都擴展了 ViewDataBinding。
創(chuàng)建
應(yīng)該在 inflate 之后立即創(chuàng)建綁定,以確保 View 層次結(jié)構(gòu)不受干擾。 有幾種方法可以綁定到布局。 最常見的是在綁定類中使用靜態(tài)方法。inflate 方法 inflate View 層次結(jié)構(gòu),一步到位。 有一個更簡單的版本,只需要一個 LayoutInflater 和一個 ViewGroup:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);如果布局使用不同的機制 inflate,它可能會被分開綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);有時綁定不能預(yù)先知道。 在這種情況下,可以使用 DataBindingUtil 類創(chuàng)建綁定:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);Views With IDs
將在布局中為每個視圖生成一個公開的 final 字段。 該綁定在視圖層次結(jié)構(gòu)上執(zhí)行單個傳遞,提取帶有 ID 的視圖。 這個機制可以比調(diào)用多個視圖的 findViewById 更快。 例如:
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="user" type="com.example.User"/></data><LinearLayoutandroid: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;IDs 不像沒有數(shù)據(jù)綁定那樣必要,但是仍然有一些情況下代碼需要訪問視圖。
變量
每個變量將被賦予訪問器方法。
<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
ViewStub 與普通視圖有點不同。 他們從不可見的時候開始,當(dāng)他們要么變得可見時,要么被明確告知 inflate 時,他們通過 inflate 另一個布局來取代布局。
由于 ViewStub 本質(zhì)上從視圖層次中消失,所以綁定對象中的視圖也必須消失以允許收集。 因為視圖是 final 的,所以 ViewStubProxy 對象代替了ViewStub,當(dāng) ViewStub 存在時,開發(fā)人員可以訪問 ViewStub,并且在 ViewStub被 inflate 時也可以訪問被 inflate 的視圖。
當(dāng) inflate 另一個布局時,必須為新的布局建立綁定。因此,ViewStubProxy 必須偵聽 ViewStub 的 ViewStub.OnInflateListener 并在此時建立綁定。由于只有一個可以存在,ViewStubProxy 允許開發(fā)者在建立綁定之后設(shè)置一個 OnInflateListener 對象。
高級綁定
動態(tài)變量
有時,特定的綁定類將不被知道。 例如,針對任意布局的 RecyclerView.Adapter 將不知道具體的綁定類。 它仍然必須在 onBindViewHolder(VH,int) 期間分配綁定值。
在這個例子中,RecyclerView 綁定的所有布局都有一個 item 變量。BindingHolder 有一個返回 ViewDataBinding 基類的 getBinding 方法。
public void onBindViewHolder(BindingHolder holder, int position) {final T item = mItems.get(position);holder.getBinding().setVariable(BR.item, item);holder.getBinding().executePendingBindings(); }立即綁定
當(dāng)變量或 observable 變化時,綁定將被安排在下一幀之前改變。但有時候,綁定必須立即執(zhí)行。要強制執(zhí)行,請使用 executePendingBindings() 方法。
后臺線程
只要不是集合,就可以在后臺線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在評估時本地化每個變量/字段,以避免任何并發(fā)問題。
屬性設(shè)置
每當(dāng)綁定值發(fā)生變化時,生成的綁定類必須使用綁定表達式在視圖上調(diào)用setter方法。 數(shù)據(jù)綁定框架可以自定義調(diào)用哪個方法來設(shè)置值。
自動的設(shè)置器
對于一個屬性,數(shù)據(jù)綁定將試圖找到設(shè)置屬性的方法。屬性的命名空間并不重要,只有屬性名稱本身才重要。例如,與 TextView 的屬性 android:text 相關(guān)聯(lián)的表達式將查找 setText(String)。 如果表達式返回 int,那么數(shù)據(jù)綁定將搜索一個 setText(int) 方法。請注意讓表達式返回正確的類型,如果需要的話就進行轉(zhuǎn)換。即使給定名稱不存在任何屬性,數(shù)據(jù)綁定也可以工作。 然后,您可以使用數(shù)據(jù)綁定輕松地為任何 setter 創(chuàng)建屬性。 例如,support 庫中的 DrawerLayout 沒有任何屬性,但是有很多 setter。 您可以使用自動設(shè)置器來使用其中的一個。
<android.support.v4.widget.DrawerLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:scrimColor="@{@color/scrim}"app:drawerListener="@{fragment.drawerListener}"/>重命名設(shè)置器
一些屬性的設(shè)置器會與名稱不匹配。 對于這些方法,一個屬性可能通過 BindingMethods 注解與設(shè)置器關(guān)聯(lián)。 這必須與一個類相關(guān)聯(lián),每個重命名的方法一個包含一個 BindingMethod 注解。例如,android:tint 屬性確實與 setImageTintList(ColorStateList) 關(guān)聯(lián),而不是 setTint。
@BindingMethods({@BindingMethod(type = "android.widget.ImageView",attribute = "android:tint",method = "setImageTintList"), })開發(fā)人員不太可能需要重命名設(shè)置器, 安卓框架已經(jīng)為這些屬性實現(xiàn)了。
自定義設(shè)置器
一些屬性需要自定義綁定邏輯。 例如,android:paddingLeft 屬性沒有關(guān)聯(lián)的設(shè)置器。 相反,setPadding(eft, top, right, bottom) 存在。 使用 BindingAdapter 注釋的靜態(tài)綁定適配器方法允許開發(fā)人員自定義如何調(diào)用屬性的設(shè)置器。
安卓屬性已經(jīng)創(chuàng)建了 BindingAdapters。 例如,這里是 paddingLeft:
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) {view.setPadding(padding,view.getPaddingTop(),view.getPaddingRight(),view.getPaddingBottom()); }綁定適配器對其他類型的自定義非常有用。 例如,一個自定義的加載器可以被調(diào)用脫機線程來加載一個圖像。當(dāng)發(fā)生沖突時,開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器。您也可以讓適配器接收多個參數(shù)。
@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}"/>如果 imageUrl 和 error 都用于 ImageView 且 imageUrl 是字符串,并且 error 是 drawable,則將調(diào)用此適配器。
自定義名稱空間在匹配過程中被忽略。
也可以為 android 命名空間編寫適配器。
綁定適配器方法可以選擇在其處理程序中使用舊值。 采用新舊值的方法,應(yīng)該把屬性的所有舊的值放在第一位,然后是新的值:
事件處理器只能用于只有一個抽象方法的接口或抽象類。例如:
@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);}} }當(dāng)一個監(jiān)聽器有多個方法時,它必須被分成多個監(jiān)聽器。例如,View.OnAttachStateChangeListener 有兩個方法:onViewAttachedToWindow() 和 onViewDetachedFromWindow()。然后我們必須創(chuàng)建兩個接口來區(qū)分它們的屬性和處理器。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow {void onViewDetachedFromWindow(View v); }@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow {void onViewAttachedToWindow(View v); }因為更改一個偵聽器也會影響另一個偵聽器,所以我們必須有三個不同的綁定適配器,一個用于每個屬性,另一個用于兩個,它們都應(yīng)該被設(shè)置。
@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() {@Overridepublic void onViewAttachedToWindow(View v) {if (attach != null) {attach.onViewAttachedToWindow(v);}}@Overridepublic 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);}} }上面的例子比正常情況稍微復(fù)雜一點,因為視圖對偵聽器使添加和刪除,而不是對 View.OnAttachStateChangeListener 使用set方法。 android.databinding.adapters.ListenerUtil 類有助于跟蹤以前的監(jiān)聽器,以便它們可以在綁定適配器中被移除。通過使用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 注解接口 OnViewDetachedFromWindow 和OnViewAttachedToWindow,數(shù)據(jù)綁定代碼生成器知道只應(yīng)在 API 12 或以上的設(shè)備上調(diào)用 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 來運行運行偵聽器。
轉(zhuǎn)換器
對象轉(zhuǎn)換
從綁定表達式返回一個對象時,將從自動,重命名和自定義的設(shè)置器中選擇一個設(shè)置器。 該對象將被轉(zhuǎn)換為所選設(shè)置器的參數(shù)類型。
這對于那些使用 ObservableMaps 來保存數(shù)據(jù)的開發(fā)者來說是很方便的。例如:
userMap 返回一個對象,該對象將被自動轉(zhuǎn)換為在 setText(CharSequence) 中找到的參數(shù)類型。 當(dāng)參數(shù)類型可能混淆時,開發(fā)者需要在表達式中輸入。
自定義轉(zhuǎn)換
有時轉(zhuǎn)換應(yīng)該在特定類型之間自動進行。 例如,設(shè)置 background 時:
<Viewandroid:background="@{isError ? @color/red : @color/white}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>在這里,背景需要一個 Drawable,但是顏色是一個整數(shù)。每當(dāng)一個 Drawable 被判斷該返回一個整數(shù)時,該整形應(yīng)該被轉(zhuǎn)換成一個 ColorDrawable。 這個轉(zhuǎn)換是通過一個帶有 BindingConversion 注解的靜態(tài)方法完成的:
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) {return new ColorDrawable(color); }請注意,轉(zhuǎn)換只發(fā)生在設(shè)置器級別,所以不允許混合類型,如下所示:
<Viewandroid:background="@{isError ? @drawable/error : @color/white}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>Android Studio 支持?jǐn)?shù)據(jù)綁定
Android Studio 支持?jǐn)?shù)據(jù)綁定代碼的許多代碼編輯功能。例如,它支持?jǐn)?shù)據(jù)綁定表達式的以下功能:
- 語法高亮
- 表達式語法錯誤的提示
- XML代碼完成
- 包括導(dǎo)航(如導(dǎo)航到聲明)和快速文檔的參考
注意:如果沒有錯誤,則數(shù)組和一般類型(如 Observable 類)可能會顯示錯誤。
預(yù)覽窗格顯示數(shù)據(jù)綁定表達式的默認(rèn)值(如果提供的話)。在以下示例摘錄布局XML文件中的元素時,預(yù)覽窗格將在 TextView 中顯示 PLACEHOLDER 默認(rèn)文本值。
<TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName, default=PLACEHOLDER}"/>如果需要在項目設(shè)計階段顯示默認(rèn)值,則還可以使用工具屬性而不是默認(rèn)表達式值,如 Design Time Layout Attributes 中所述。
原文地址:https://developer.android.google.cn/topic/libraries/data-binding/index.html
贈送源碼:GitHub - Pangu-Immortal/MagicWX: 🔥免root實現(xiàn) Android改機(一鍵新機)技術(shù)解密,微信無限多開等。。
《最完整的Android逆向知識體系》
Pangu-Immortal (Pangu-Immortal) · GitHub
總結(jié)
以上是生活随笔為你收集整理的谷歌推荐Data Binding实现MVVM模式(完整文档)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android面向切面编程框架(Aspe
- 下一篇: 从Zygote孵化frameworks进