Data Binding(數據綁定)用戶指南

http://www.jianshu.com/p/b1df61a4df77javascript

 

1)介紹

 

這篇文章介紹瞭如何使用Data Binding庫來寫聲明的layouts文件,而且用最少的代碼來綁定你的app邏輯和layouts文件。php

Data Binding庫不只靈活並且普遍兼容- 它是一個support庫,所以你能夠在全部的Android平臺最低能到android2.1(API等級7+)上使用它。css

需求:Android Plugin for Gradle 1.5.0-alpha1 或 更高版本html

2)構建環境

要開始使用Data Binding,首先須要在Android SDK Manager的支持庫裏下載該庫。java

你的app要使用Data Binding,須要添加Data Binding到gradle構建文件裏,以下:android

android {
    ....
    dataBinding {
        enabled = true } }

Data Binding插件將會在你的項目內添加必需提供的以及編譯配置依賴。swift

請確保您使用的是Android Studio的兼容版本。Android Studio的Data Binding插件須要Android Studio 1.3.0 或 更高版本api

3)Data Binding Layout文件

a)Data Binding表達式

Data Binding layout文件有點不一樣的是:起始根標籤是layout,接下來一個data元素以及一個view的根元素。這個view元素就是你沒有使用Data Binding的layout文件的根元素。舉例說明以下:數組

<?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內描述了一個名爲user的變量屬性,使其能夠在這個layout中使用:架構

<variable name="user" type="com.example.User"/>

在layout的屬性表達式寫做@{},下面是一個TextView的text設置爲user的firstName屬性:

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
b)Data對象

假設你有一個user的plain-old Java Object(POJO):

public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }

這個類型的對象擁有從不改變的數據。在app中它是常見的,能夠讀取一次而且以後從不改變。固然也可使用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; } }

從Data Binding的角度來看,這兩個類是等價的。用於TextView中的android:text屬性的表達式@{user.firstName}將訪問前者POJO對象中的firstName和後者JavaBeans對象中的getFirstName()方法。

c)Binding數據

默認狀況下,一個Binding類會基於layout文件的名稱而產生,將其轉換爲Pascal case(譯註:首字母大寫的命名規範)而且添加「Binding」後綴。上述的layout文件是activity_main.xml,所以生成的類名是ActivityMainBinding。此類包含從layout屬性到layout的Views中全部的bindings(例如user變量),而且它還知道如何給Binding表達式分配數值。建立bindings的最簡單的方式是在inflating(譯註:layout文件與Activity/Fragment的「連接」)期間以下:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User("Test", "User"); binding.setUser(user); }

就是這樣,運行app後,你將會看到Test User。或者你能夠經過以下獲取View:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

若是你在ListView或者RecyclerView adapter使用Data Binding時,你可能會使用:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); //or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
d)事件處理

數據綁定容許你編寫表達式來處理view分派的事件。事件屬性名字取決於監聽器方法名字。例如View.OnLongClickListeneronLongClick()的方法,所以這個事件的屬性是android:onLongClick。處理事件有兩種方法:

4)深刻Layout文件

a)Import

零個或多個import元素可能在data元素中使用。這些只用在你的layout文件中添加引用,就像在Java中:

<data>
    <import type="android.view.View"/> </data>

如今,View可使用你的Binding表達式:

<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"/>

這樣,在該layout文件中Vista對應com.example.real.estate.View,而View對應android.view.View。導入的類型能夠在Variable和表達式中使用做爲引用來使用:

<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尚未處理imports,因此自動導入Variable在你的IDE不能使用。您的app仍會正常編譯,你能夠在您的Variable定義中使用徹底符合規定的名稱來解決該IDE問題。

<TextView android:text="@{((User)(user.connection)).lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>

導入的類型還能夠在表達式中使用static屬性和方法:

<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。*是自動導入的。

b)Variables

data中可使用任意數量的variable元素。每個variable元素描述了一個用於layout文件中Binding表達式的屬性。

<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>

Variable類型在編譯時檢查,所以若是一個Variable實現了Observableobservable collection,這應該反映在類型中。(譯註:須要查找資料來理解)若是variable是一個沒有實現Observable接口的基本類或者接口,Variables不會被observed!

當對於多種配置有不一樣的layout文件時(如,橫向或縱向),Variables會被合併。這些layout文件之間必須不能有衝突的Variable定義。

產生的Binding類對於每個描述的Variables都會有setter和getter。這些Variables會使用默認的Java值 - null(引用類型)、0(int)、false(boolean)等等,直到調用setter時。

c)自定義Binding類名稱

默認狀況下,Binding類的命名是基於所述layout文件的名稱,用大寫開頭,除去下劃線()以及()後的第一個字母大寫,而後添加「Binding」後綴。這個類將被放置在一個模塊封裝包裏的databinding封裝包下。例如,所述layout文件contact_item.xml將生成ContactItemBinding。若是模塊包是com.example.my.app,那麼它將被放置在com.example.my.app.databinding

Binding類可經過調整data元素中的class屬性來重命名或放置在不一樣的包中。例如:

<data class="ContactItem"> ... </data>

在模塊封裝包的databinding包中會生成名爲ContactItem的Binding類。若是要想讓該類生成在不一樣的包種,你須要添加前綴.,以下:

<data class=".ContactItem"> ... </data>

在這個狀況下,ContactItem類直接在模塊包種生成。或者你能夠提供整個包名:

<data class="com.example.ContactItem"> ... </data>
d)Includes

經過使用application namespace以及在屬性中的Variable名字從容器layout中傳遞Variables到一個被包含的layout:

<?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兩個layout文件中必須要有user variable

e)表達式
  • 經常使用表達式跟Java表達式很像,如下這些是同樣的:

    • 數學 + - / * %
    • 字符串鏈接 +
    • 邏輯 && ||
    • 二進制 & | ^
    • 一元運算 + - ! ~
    • 移位 >> >>> <<
    • 比較 == > < >= <=
    • instanceof
    • 分組 ()
    • null
    • Cast
    • 方法調用
    • 數據訪問 []
    • 三元運算 ?:

    • 示例:

      android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
  • 缺乏的操做:

    • this
    • super
    • new
    • 顯式泛型調用
  • Null合併操做

    • ?? - 左邊的對象若是它不是null,選擇左邊的對象;或者若是它是null,選擇右邊的對象:
      android:text="@{user.displayName ?? user.lastName}"
  • 函數上的寫法以下:

    android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • 屬性引用
    第一個已經在前邊提到了a)Data Binding表達式:JavaBean引用的簡短格式。
    當一個表達式引用一個類的屬性,它仍使用一樣的格式對於字段、getters以及ObservableFields。

    android:text="@{user.lastName}"
  • 避免 NullPointerException
    Data Binding代碼生成時自動檢查是否爲nulls來避免出現null pointer exceptions錯誤。例如,在表達式@{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="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"]}"
  • Resources
    使用正常的表達式來訪問resources也是可行的:

    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)}"

    一些資源須要顯式類型判斷:

類型 正常引用 表達式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

5)Data 對象

任何Plain old java object(PO​​JO)可用於Data Binding,但修改POJO不會致使UI更新。Data Binding的真正能力是當數據變化時,能夠通知給你的Data對象。有三種不一樣的數據變化通知機制:Observable對象、ObservableFields以及observable collections

當這些可觀察Data對象​​綁定到UI,Data對象屬性的更改後,UI也將自動更新。

a)Observable 對象

實現android.databinding.Observable接口的類能夠容許附加一個監聽器到Bound對象以便監聽對象上的全部屬性的變化。

Observable接口有一個機制來添加和刪除監聽器,但通知與否由開發人員管理。爲了使開發更容易,一個BaseObservable的基類爲實現監聽器註冊機制而建立。Data實現類依然負責通知當屬性改變時。這是經過指定一個Bindable註解給getter以及setter內通知來完成的。

private static class User extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getFirstName() { 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類文件中生成一個Entry。BR類文件會在模塊包內生成。若是用於Data類的基類不能改變,Observable接口經過方便的PropertyChangeRegistry來實現用於儲存和有效地通知監聽器。

b)Observable 字段

一些小工做會涉及到建立Observable類,所以那些想要節省時間或者幾乎沒有幾個屬性的開發者可使用ObservableFieldsObservableFields是自包含具備單個字段的observable對象。它有全部基本類型和一個是引用類型。要使用它須要在data對象中建立public 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();
c)Observable 集合

一些app使用更多的動態結構來保存數據。Observable集合容許鍵控訪問這些data對象。ObservableArrayMap用於鍵是引用類型,如String

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);

在layout文件中,經過String鍵能夠訪問map:

<data> <import type="android.databinding.ObservableMap"/> <variable name="user" type="ObservableMap<String, Object>"/> </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用於鍵是整數:

ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);

在layout文件中,經過索引能夠訪問list:

<data> <import type="android.databinding.ObservableList"/> <import type="com.example.my.app.Fields"/> <variable name="user" type="ObservableList<Object>"/> </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"/>

6)Binding生成

Binding類的生成連接了layout中variables與Views。如前面所討論的,Binding的名稱和包名能夠定製。所生成的Binding類都擴展了android.databinding.ViewDataBinding

a)建立

Binding應在inflation以後就立馬建立,以確保View層次結構不在以前打擾layout中的binding到views上的表達式。有幾個方法能夠綁定到一個layout。最多見的是在Binding類上使用靜態方法.inflate方法載入View的層次結構而且綁定到它只需這一步。還有一個更簡單的版本,只須要LayoutInflater還有一個是採用ViewGroup

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(LayoutInflater, viewGroup, false);

若是使用不一樣的機制載入layout,他可一分開綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時Binding不能提早知道,對於這種狀況,可使用DataBindingUtil類來建立Binding:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
b)帶ID的Views

在layout中對於每一個帶ID的View會生成一個public final字段。Binding在View層次結構上作單一的傳遞,提取帶ID的Views。這種機制比起某些Views使用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>

它會生成以下的Binding類:

public final TextView firstName; public final TextView lastName;

IDs不像沒有Data Bindings那樣幾乎沒有必要,可是仍然會有一些實例須要從代碼中訪問Views。

c)Variables

每一個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>

它會在Binding中生成setters和getters:

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);
d)ViewStubs

ViewStubs跟正常的Views略有不一樣。他們開始時是不可見的,當他們要麼設置爲可見或被明確告知要載入時,它們經過載入另一個layout取代了本身。

因爲ViewStub基本上從View的層次結構上消失,在Binding對象的View也必須消失來容許被收集。由於Views是最後的,一個ViewStubProxy對象取帶ViewStub,給開發者得到了ViewStub,當它存在以及還能夠訪問載入的View層次結構時當ViewStub已被載入時。

當載入另外一個layout,爲新的佈局必需建立一個Binding。所以,ViewStubProxy必需監聽ViewStubOnInflateListener監聽器並在那個時候創建Binding。由於只有一個能夠存在,ViewStubProxy容許開發者在其上設置一個OnInflateListener它會在創建Binding後調用。

e)Binding進階
  • 動態Variables

有時,不知道具體的Binding類,例如,一個RecyclerView適配器對layouts任意操做並不知道具體的Binding類。它仍然必需在onBindViewHolder期間賦值給Binding。

在這個例子中,該RecyclerView綁定的全部layouts有一個「item」的Variable。該BindingHolder有一個getBinding方法返回ViewDataBinding

public void onBindViewHolder(BindingHolder holder, int position) { final T item = mItems.get(position); holder.getBinding().setVariable(BR.item, item); holder.getBinding().executePendingBindings(); }
  • 直接Binding

當一個variable或observable變化時,binding會在下一幀以前被計劃要改變。有不少次,可是在Binding時必須當即執行。要強制執行,使用executePendingBindings()方法。

  • 後臺線程

只要它不是一個集合,你能夠在後臺線程中改變你的數據模型。在判斷是否要避免任何併發問題時,Data Binding會對每一個Varialbe/field本地化。

7)屬性Setters

每當綁定值的變化,生成的Binding類必須調用setter方法​​。Data Binding框架有能夠自定義賦值的方法。

a)自動Setters

對於一個屬性,Data Binding試圖找到setAttribute方法。與該屬性的namespace並不什麼關係,僅僅與屬性自己名稱有關。

例如,有關TextView的android:text屬性的表達式會尋找一個setText(String)的方法。若是表達式返回一個int,Data Binding會搜索的setText(int)方法。注意:要表達式返回正確的類型,若是須要的話使用casting。Data Binding仍會工做即便沒有給定名稱的屬性存在。而後,您能夠經過Data Binding輕鬆地爲任何setter「創造」屬性。例如,DrawerLayout沒有任何屬性,但大量的setters。您可使用自動setters來使用其中的一個。

<android.support.v4.widget.DrawerLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}"/>
b)重命名的Setters

一些有setters的屬性按名稱並不匹配。對於這些方法,屬性能夠經過BindingMethods註解相關聯。這必須與一個包含BindingMethod註解的類相關聯,每個用於一個重命名的方法。例如,android:tint屬性與setImageTintList相關聯,而不與setTint相關。

@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })

以上例子,開發者須要重命名setters是不太可能了,android架構屬性已經實現了。

c)自定義Setters

有些屬性須要自定義綁定邏輯。例如,對於android:paddingLeft屬性並無相關setter。相反,setPadding(left, top, right, bottom)是存在在。一個帶有BindingAdapter註解的靜態綁定適配器方法容許開發者自定義setter如何對於一個屬性的調用。

Android的屬性已經創造了BindingAdapters。舉例來講,對於paddingLeft

@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }

Binding適配器對其餘定製類型很是有用。例如,自定義loader能夠用來異步載入圖像。

當有衝突時,開發人員建立的Binding適配器將覆蓋Data Binding默認適配器。

您也能夠建立能夠接收多個參數的適配器。

@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}」/>

若是對於一個ImageViewimageUrlerror都被使用而且imageUrl是一個string類型以及error是一個drawable時,該適配器會被調用。

  • 匹配的過程當中自定義namespaces將被忽略。
  • 你也能夠爲Android namespaces寫適配器。

8)轉換

a)對象轉換

當從Binding表達式返回一個對象,一個setter會從自動、重命名以及自定義的setters中選擇。該對象將被轉換爲所選擇的setter的參數類型。

這是爲了方便那些使用ObservableMaps來保存數據。例如:

<TextView android:text='@{userMap["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>

userMap返回一個對象而且該對象將自動轉換爲setText(CharSequence)的參數類型。當有關參數類型可能混亂時,開發人員須要在表達式中轉換。

b)自定義轉換

有時候轉換應該是自動的在特定類型之間。例如,設置背景的時候:

<View
   android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>

這裏,背景須要Drawable對象,但顏色是一個整數。無論什麼時候有Drawable而且返回值是一個整數,那麼整數類型會被轉換爲ColorDrawable。這個轉換是經過使用帶有BindingConversion註解的靜態方法完成的:

@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }

注意:轉換僅僅發生在setter級別,所以它是不容許如下混合類型:

<View
   android:background="@{isError ? @drawable/error : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>

9)Android Studio支持

Android Studio爲數據綁定支持許多的代碼編輯。例如,它支持如下功能:

注意: 數組以及通用類型,好比說Observable類,可能會顯示錯誤事實上並無錯誤。

預覽面板會顯示數據綁定的默認值。在如下例子中,面板會在TextView中顯示PLACEHOLDER默認值

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName, default=PLACEHOLDER}"/>

若是你須要在設計階段就顯示默認值,你可使用工具屬性來代替默認表達數值,參考:Designtime Layout Attributes

---------------------<完>-----------------------

原文連接:Data Binding Guide (Android)

(若有翻譯有誤或者不理解的地方,請指正)

 

原文連接:http://www.jianshu.com/p/b1df61a4df77
相關文章
相關標籤/搜索