Android架構元件-DataBinding的使用

weixin_34050427發表於2018-07-06
什麼是DataBinding

Data Binding,顧名思義,資料繫結,是Google對MVVM在Android上的一種實現,可以直接繫結資料到xml中,並實現自動重新整理。現在最新的版本還支援雙向繫結,儘管使用場景不是那麼多。

Data Binding可以提升開發效率(節省很多以往需要手寫的java程式碼),效能高(甚至超越手寫程式碼),功能強(強大的表示式支援)。

用途

去掉Activities & Fragments內的大部分UI程式碼(setOnClickListener, setText, findViewById, etc.)
XML變成UI的唯一真實來源
減少定義view id的主要用途(資料繫結直接發生在xml)

優勢
  • UI程式碼放到了xml中,佈局和資料更緊密
  • 效能超過手寫程式碼
  • 保證執行在主執行緒
劣勢
  • IDE支援還不那麼完善(提示、表示式)
  • 報錯資訊不那麼直接
  • 重構支援不好(xml中進行重構,java程式碼不會自動修改)
DataBinding的使用

Android Studio 的相容版本,需要 1.3 及以上的版本.
在Android Studio上使用,需要在module級別的build.gradle上新增對DataBinding的支援:

android {
    ...
    dataBinding {
        enabled = true
    }
}
  1. 資料繫結佈局檔案XML

資料繫結的xml和我們以前經常寫的xml稍有不同,從佈局的跟標記開始,依次是根佈局layout,資料元素data節點,檢視元素,例如:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
>
   <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 name="user" type="com.example.User"/>

佈局中的表示式使用 "@{}" 語法在屬性中寫入,在這裡,TextView 的文字設定為使用者 FirstName 屬性:

<TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
  1. 資料物件User:
class UserModel {
    var lastName: String? = null
    var firstName: String? = null
    ···
}
  1. 資料繫結

生成例項,會有一定的規則,layout通過檔名生成,View通過id生成,通過dataBinding.setVariable(Variable variable);來實現資料的繫結。
預設情況下,繫結類將根據 layout 檔案的名稱生成,首字母大寫的命名規範,並新增 "Binding" 字尾,上述的佈局檔案是main_activity.xml,所以生成類是 MainActivityBinding, 該類將佈局屬性(例如 User 變數)的所有繫結儲存到佈局檢視中,並知道如何為繫結表示式賦值,建立最簡單的方法是在 inflating 時繫結,如下:

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

如果你是在 RecyclerView adapter 中使用 Data Binding 時,你可能使用:

override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): AdapterBindingViewHolder<*> {
        var binding: ViewDataBinding = DataBindingUtil.inflate(mLayoutInflater, R.layout.book_recycle_item, parent, false)
        return AdapterBindingViewHolder(binding)
    }

通過自定義類名,這樣就可以避開上面的規則

<data class="CustomBinding"></data> //在app_package/databinding下生成CustomBinding;
<data class=".CustomBinding"></data> //在app_package下生成CustomBinding;
<data class="com.example.CustomBinding"></data> // 明確指定包名和類名。
  1. 事件處理

資料繫結允許你編寫表示式處理,如 onClick 事件

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{() -> viewModel.onClickBindalbeTest()}"
    android:text="click bindalbe test"
 />

可以使用多個引數的 Lambda 表示式:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

<CheckBox 
    android:layout_width="wrap_content"     
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

還可以使用三元表示式:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
  1. XML data的使用

可以在資料元素內使用零個或多個匯入元素。這些可以參考在你的佈局檔案的類,就像在 java:

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

現在,檢視可以在繫結表示式中使用:

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

在表示式中引用靜態欄位和方法時也可以使用匯入型別:

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

可以在資料元素內使用任意數量的變數元素。每個變數元素描述可以在佈局檔案中用於繫結表示式中的佈局的屬性:

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

還可以自定義繫結類名
通過調整資料元素的類屬性可以將繫結類重新命名或放置在不同的包中。例如:

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

這個生成繫結類中的模組封裝在資料繫結包 ContactItem,如果類產生在不同的包中的模組封裝內,它可能會加 ".",如下:

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

在這種情況下,ContactItem 直接在模組封裝生成,如果提供完整的包,任何包可以使用:

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

通過使用應用程式名稱空間和屬性中的變數名,可以將變數從包含佈局中傳遞到包含佈局中:

<?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}"/>
   </LinearLayout>
</layout>
  1. 表示式

支援的運算子:
<ul>
<li>數學運算子: + - / * %</li>
<li>字串拼接: +</li>
<li>邏輯運算子: && ||</li>
<li>二進位制: & | ^</li>
<li>一元運算子: +</li>
<li>位運算子: >> >>> <<</li>
<li>比較: == > < >= <=</li>
<li>instanceof</li>
<li>()</li>
<li>資料型別: character, String, numeric, null</li>
<li>型別轉換(ClassCast)</li>
<li>方法回撥(Method calls)</li>
<li>資料屬性</li>
<li>陣列:[]</li>
<li>三元操作符:?</li>
</ul>

使用如下:

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

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

<string name="nameFormat">%s -  %s</string>

android:text="@{@string/nameFormat(user.displayName , user.lastName)}"

也可以直接資料處理

android:text="@{Integer.toString(user.age)}"
  1. Observable Objects

目前所提供的ObservableField有:

Observable型別 對應原型別
ObservableArrayList ArrayList
ObservableArrayMap ArrayMap
ObservableBoolean boolean
ObservableByte byte
ObservableChar char
ObservableFloat float
ObservableDouble double
ObservableLong long
ObservableInt int
ObservableParcelable<T extends Parcelable> <T extends Parcelable>
ObservableField<T> <T>

Observable是提供新增移除監聽的一個java介面,DataBinding基於此介面提供了一個基礎類BaseObserable,我們可以這樣使用它,通過Bindale註解繫結一個getter,當data屬性發生改變在setter中發出通知,這樣就實現了響應

/**
 * 繼承BaseObservable
 * set方法notifyPropertyChanged
 * get方法@Bindable
 */
class BindalbeTestModel: BaseObservable() {
    var testStr: String? = null
        set(value) {
            field = value
            notifyPropertyChanged(com.ghp.demo.databindingdemoproject.BR.testStr)
        }
        @Bindable
        get() {
            return field?:""
        }
}

根據google為我們提供了一些Obserable類,還可以這樣:

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

看這樣的UserModel會不會覺得煩瑣,其實還可以這樣

class UserModel {
    var firstName: String? = null
    var userAge: Int = 0
    var lastName: String? = null
}

var user = ObservableField<UserModel>()

/**
 * ObservableField可以包裹變數非Observable的model
 * model初始化後,更改值,必須要notifyChange重新整理UI
 */
fun onclickObservableFieldUser() {
    user.get().firstName = "ghp"
    user.notifyChange()
}

ObservableArrayMap:

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

在xml中使用:

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

xml使用:

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

參考:
DataBinding使用全面詳解
Android DataBinding 詳解
從零開始的Android新專案7 - Data Binding入門篇

相關文章