1. 程式人生 > >Android Data binding官方指南

Android Data binding官方指南

1. 佈局和繫結表示式

表示式語言允許您編寫處理由檢視分發的事件的表示式。資料繫結庫自動生成將佈局中的檢視與資料物件繫結的類。

資料繫結佈局檔案稍有不同,以佈局的根標記開始,後面跟著資料元素和檢視根元素。此檢視元素是您的根將在非繫結佈局檔案中的內容。下面的程式碼顯示了一個示例佈局檔案:

(1)佈局檔案這樣寫

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

資料中的user變數描述了可以在該佈局中使用的屬性。

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

佈局中的表示式使用“@ {}”語法寫入屬性。這裡,TextView文字被設定為使用者變數的firstName屬性:

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

注意:佈局表示式應該保持小而簡單,因為它們不能進行單元測試,並且IDE支援有限。為了簡化佈局表示式,可以使用自定義繫結介面卡

1.1 資料物件

這裡假設你有一個普通的物件用於描述User實體:

KOTLIN

data class User(val firstName: String, val lastName: String)

JAVA

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

在資料繫結中,訪問@{user.firstName},那麼預設就會訪問同名的屬性firstName,或對應的getFirstName方法。

這種型別的物件具有從不改變的資料。在應用中常見的資料是一次讀取,此後不改變。也可以使用遵循一組約定的物件,如Java中的訪問器方法的使用,如下面的示例所示:

KOTLIN

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

JAVA

1.2 繫結資料

預設情況下,繫結類將基於佈局檔案的名稱來產生
如佈局檔案為main_activity.xml,這樣生成的類是 MainActivityBinding
如下設定Activity的contentView:

KOTLIN

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

JAVA

@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);
}

上文說,在xml中用表示式可以獲取資料,那麼同樣的,在程式碼中也可以設定資料,如這裡的:binding.setUser(user),還可以使用inflate的方式來獲取生成資料繫結類:

KOTLIN

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

JAVA

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

如果是在一個adapter中來使用,例如Fragment, ListView, or RecyclerView 內部的adapter,

你可能傾向於使用bindings類的inflate()方法或者DataBindingUtil類,如下樣例程式碼所示:

KOTLIN

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

JAVA

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

1.3 表示式語言

1.3.1 共同的特徵

表示式語言看起來很像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 ?:

例:

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

1.3.2 不支援的操作符

thisnewsuper 如這些,還是隻能在java類中使用

1.3.3 合併null操作符

如下使用 “??” 操作符

android:text="@{user.displayName ?? user.lastName}"

等價於

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

1.3.4 屬性引用

表示式可以通過如下格式引用一個類的屬性,欄位,getters,和ObservableField物件與此相同,如下,user有getLastName(),可以簡單引用:

android:text="@{user.lastName}"

1.3.5 NullPointerException處理

資料繫結程式碼會自動檢查null值,避免NullPointerException。例如String型別預設為null, Int型別預設0.

1.3.6 集合

一般的集合都可以使用”[]”操作符: arrays, lists, sparse lists, and 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]}"

1.3.7 字串常量引用

如下,可以使用單引號在屬性值上,用雙引號在字串字面值上:

android:text='@{map["firstName"]}'

也可以反過來

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"

1.3.8 資源

可以使用正常的訪問資源的表示式語法:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

可以為格式字串和複數提供引數:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

一些資源需要顯示的引用

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

2. 事件處理

資料繫結允許您編寫處理從檢視中分發的事件的表示式(例如,onClick()方法)。事件屬性名稱由偵聽器方法的名稱來確定,但有少數例外。例如,View.OnClickListener有一個方法onClick(),所以這個事件的屬性是android:onClick。

有一些用於click事件的專用事件處理程式,它們需要除android:onClick之外的屬性以避免衝突。可以使用以下屬性來避免這些型別的衝突:

Class Listener setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

您可以使用以下機制來處理事件:

  • 方法引用:在表示式中,可以引用符合偵聽器方法簽名的方法。當表示式計算為方法引用時,資料繫結將偵聽器中的方法引用和所有者物件封裝起來,並將該偵聽器設定在目標檢視上。如果表示式計算為NULL,則資料繫結不建立偵聽器,並設定空偵聽器。

  • 偵聽器繫結:這些lambda表示式是在事件發生時被計算的。資料繫結總是建立一個監聽器,監聽器設定在檢視上。當事件被分發時,偵聽器呼叫lambda表示式。

2.1 方法引用

比如定義一個符合OnClickListener#onClick(View view)方法引數簽名規則的方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

如下在xml中繫結並使用:

<?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="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

2.2 監聽器繫結

表示式使用的方法就可以定義的比較隨意了,不用像”方法引用”那樣遵循特定規則,如一個方法

public class Presenter {
    public void onSaveClick(Task task){}
}

xml中使用它

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

像上面,我們並沒有定義onClick(android.view.View) 引數view。監聽器繫結,允許我們忽略所有的引數。如果想要使用引數,可以忽略引數型別,隨意定義一個名字。監聽器繫結還有很多靈活的寫法,可以參考官方文件。

3. 匯入、變數和包含

資料繫結庫提供了諸如匯入、變數和包含的特徵。匯入使佈局檔案中的類易於引用。變數允許您描述可以在繫結表示式中使用的屬性。包括讓您重用應用程式中的複雜佈局。

3.1 匯入

匯入允許您輕鬆地在佈局檔案中引用類,就像託管程式碼一樣。可以在資料元素內部使用零個或多個匯入元素。下面的程式碼示例將View類匯入佈局檔案:

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

匯入View類允許您從繫結表示式中引用它。下面的示例演示如何引用View類的VISIBLEGONE常量:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

型別重新命名

當類名過長或者有類名稱衝突,其中一個類可以重新命名一個別名

<import type="com.grandstream.gsmarket.R.string" alias="Strings"/>

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="EsView"/>

其他類引用

匯入型別可以用作變數和表示式中的型別引用。下面的示例顯示使用者和列表作為變數的型別:

<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,因此變數匯入的自動補全在IDE中可能無法工作。您的應用程式仍然可以編譯,並且可以通過在變數定義中使用完全限定名來解決IDE問題。

還可以使用強制型別轉換作為表示式的一部分。下面的示例將連線屬性強制轉換為使用者型別:

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

靜態類引用

也可以在表示式中運用靜態屬性或方法,使用MyStringUtils.capitalize():

<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.Lang.*是自動匯入的。

示例程式碼

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <data>

        <variable
            name="viewmodel"
            type="com.grandstream.gsmarket.appdetail.AppDetailItemViewModel" />

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

        <import type="com.grandstream.gsmarket.R.string" alias="Strings"/>
    </data>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <android.support.v7.widget.AppCompatTextView
                        android:id="@+id/download_count_text"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="15dp"
                        android:singleLine="true"
                        android:textColor="#999999"
                        android:textSize="36px"
                        android:text='@{viewmodel.getContext().getString(Strings.download) + viewmodel.downloadCount}'/>
                </LinearLayout>

3.2Variables

可以在資料元素內部使用多個變數元素。每個變數元素描述了可以在佈局上設定的屬性,用於佈局檔案中的表示式的繫結。下面的示例宣告user, image, 和 note變數:

<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實現了Observable介面或是一個Observable的collection,那就需要在type中宣告;如果不是一個Observable的實現,那麼該variable就不會被觀察到.

當對於各種配置(例如,橫向或縱向)存在不同的佈局檔案時,將組合這些變數。在這些佈局檔案之間不存在衝突的變數定義。

所生成的繫結類對於所描述的變數中的每一個都具有setter和getter。變數採用預設託管程式碼值(null作為引用型別,0作為int,false作為boolean等等),直到setter被呼叫。

生成一個名為context的特殊變數,以便根據需要在繫結表示式中使用。context的值是從根檢視的getContext()方法中的Context物件。context變數由具有該名稱的顯式變數宣告重寫。

<data>

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

        <variable
            name="view"
            type="com.grandstream.gsmarket.apps.ApkListFragment" />

        <variable
            name="viewmodel"
            type="com.grandstream.gsmarket.apps.AppsViewModel" />

    </data>

3.3 include

variables可以傳遞給使用了include標籤的子佈局中,如

<?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和contract.xml中都可以使用變數user了

不支援在merge下直接使用include的情形。

4. 可觀察資料物件

可觀測性是指物件能夠通知他人其資料變化的能力。資料繫結庫允許您使物件、欄位或集合可觀察。

任何POJO物件都可以用於資料繫結,但是更改POJO物件,並不會引起UI更新。有三種不同的資料更改通知機制:觀察物件,觀察欄位和觀察集合。當其中一個繫結到使用者介面的可觀察的資料物件,觀察到資料物件的屬性變化,使用者介面將自動更新。

4.1 可觀察物件(Observable Objects)

類實現 Observable介面可以實現被觀察,將被允許附加一個listener,來監聽物件所有屬性的改變

Observable interface有一個機制來新增和刪除listeners,但需要通知開發者。為了讓開發變得更容易,提供一個基類,BaseObservable,它實現建立listener的註冊機制。當屬性資料改變時,資料類實現者需要響應通知。這是通過在getter上使用一個註解@Bindable,並在setter中呼叫notifyPropertyChanged()進行通知。

KOTLIN

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

JAVA

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 class,包含了資料繫結使用的資源IDs ,BR類檔案將生成在moudle的package下。如果資料類的基類不能被改變,Observable interface的實現類可以使用PropertyChangeRegistry儲存和有效地通知listeners。

4.2 可觀察屬性(ObservableFields)

如果想做比建立上面的觀察物件更少的工作,那麼可以使用ObservableField 和它的同級的一些型別:
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableShort,
ObservableInt,
ObservableLong,
ObservableFloat,
ObservableDouble, and
ObservableParcelable

它們都是一個包含單一屬性的可觀察的物件。為避免裝箱、拆箱操作,可以在資料類中定義成 public final field …

KOTLIN

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

JAVA

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

如下,使用get或set來獲取和設定屬性值:

KOTLIN

user.firstName = "Google"
val age = user.age

JAVA

user.firstName.set("Google");
int age = user.age.get();

注意:Android Studio 3.1和更高版本允許您用LiveData物件替換可觀察欄位,這對您的應用程式提供了額外的好處。有關更多資訊,請參見Use LiveData to notify the UI about data changes

4.3 可觀察集合(Observable Collections)

一些應用程式使用更多的動態結構來儲存資料。可觀察集合允許通過key訪問這些資料物件。當key是一個引用型別,如String,就可以用ObservableArrayMap

KOTLIN

ObservableArrayMap<String, Any>().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}

JAVA

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

在下面的佈局中,就可以通過String型別的key來訪問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"/>

當key是一個Integer時,可用ObservableArrayList:

KOTLIN

ObservableArrayList<Any>().apply {
    add("Google")
    add("Inc.")
    add(17)
}

JAVA

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

佈局中使用ObservableArrayList:

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

5. 生成Binding類

生成的繫結類,會連結佈局的variables以及Views。正如前面所討論的,繫結的名稱和包可以是自定義的。生成的所有繫結類都 extends ViewDataBinding

為每個佈局檔案生成繫結類。預設情況下,類的名稱基於佈局檔案的名稱,將其轉換為Pascal大小寫,並向其新增繫結字尾。上面的佈局檔名是activity_main.xml,因此相應的生成類是ActivityMainBinding。這個類儲存從佈局屬性(例如,user變數)到佈局檢視的所有繫結,並且知道如何為繫結表示式分配值。

5.1 建立繫結物件

如前文所述,由相應的佈局檔案,而生成了相應的binding class。程式碼中使用它,需要LayoutInflater,如:

KOTLIN

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(sa