DataBinding 使用介紹

DataBinding 是 Google 在 Jetpack 中推出的一款數(shù)據(jù)綁定的支持庫(kù),利用該庫(kù)可以實(shí)現(xiàn)在頁(yè)面組件中直接綁定應(yīng)用程序的數(shù)據(jù)源。使其維護(hù)起來(lái)更加方便,架構(gòu)更明確簡(jiǎn)介。

簡(jiǎn)介

那么 DataBinding 本質(zhì)上到底是個(gè)什么呢?它在開發(fā)正扮演著什么樣的角色?我們應(yīng)該如何使用?
DataBinding 名為數(shù)據(jù)綁定,他的功能很簡(jiǎn)單,就是將數(shù)據(jù)綁定在 UI 頁(yè)面上(這不是廢話嗎),明白這一點(diǎn)很重要,DataBinding 唯一的作用,也是他的使命,就是綁定數(shù)據(jù),后面的一切,以及所有的支持庫(kù),本質(zhì)上都是為了支持這個(gè)功能。

綁定一詞有兩種解釋,第一是將數(shù)據(jù)綁定在 UI 元素上;第二是將 UI 上的數(shù)據(jù)綁定到對(duì)應(yīng)的數(shù)據(jù)模型中;此外,除了將數(shù)據(jù)與 UI 綁定在一起,還要支持對(duì)數(shù)據(jù)及 UI 的變動(dòng)觀察,其中一個(gè)發(fā)生變動(dòng)就需要同步到另一個(gè)上去,也就是同步。

那么 DataBinding 對(duì)于整體架構(gòu)、對(duì)于我們開發(fā)者來(lái)說(shuō)到底意味著什么呢?由于 Android 開發(fā)語(yǔ)言的限制,最早期的 Android 開發(fā)都用 MVC 架構(gòu),導(dǎo)致代碼臃腫不堪,難以維護(hù),后來(lái)出現(xiàn)了 MVP 架構(gòu),代碼倒是清晰了起來(lái),但同樣也存在過(guò)度設(shè)計(jì)、各種接口方法滿天飛、內(nèi)存泄漏的問(wèn)題,導(dǎo)致很多人很難準(zhǔn)確的使用 MVP。無(wú)論是 MVC 還是 MVP 或多或少都存在一些問(wèn)題,始終無(wú)法找到一個(gè)完美的解決方案,其根本上是由于 Android 開發(fā)的模式本身導(dǎo)致的,我們需要先監(jiān)聽數(shù)據(jù)的變化,然后再將變化后的數(shù)據(jù)同步更新到 UI 上,這樣的步驟我們一直在重復(fù),MVC/MVP 本質(zhì)上也沒有解決這個(gè)問(wèn)題,這樣的重復(fù)性代碼我們寫了一次又一次。而 DataBinding 就是為了解決這個(gè)問(wèn)題而存在的,我們只需要將數(shù)據(jù)綁定到 UI 元素上,更新數(shù)據(jù)時(shí) UI 就會(huì)跟著改變,反之亦然,大大節(jié)省了我們的代碼。
下面就來(lái)講講他是如何實(shí)現(xiàn)的。

啟用 DataBinding

首先設(shè)置使用 Databinding,在 app module 的 build.gradle 中添加如下代碼即可:

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

布局綁定

在使用 DataBinding 時(shí)就不能按照之前的方式來(lái)編寫布局文件了,布局文件的跟布局應(yīng)該是 layout,layout 中同時(shí)存放要綁定的數(shù)據(jù)及布局,如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
?
    <data>
        <variable name="title" type="java.lang.String" />
    </data>
?
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{title}" />
    </LinearLayout>
</layout>

layout 為跟布局,data 節(jié)點(diǎn)中存放數(shù)據(jù),下面就是我們常見的布局文件。
data 中的 variable 標(biāo)簽為變量,類似于我們定義了一個(gè)變量,name 為變量名,type 為變量全限定類型名,包括包名。布局中通過(guò) @{} 來(lái)引用這個(gè)變量的值,{} 中可以是任意 Java 表達(dá)式,但不推薦使用過(guò)多的代碼。

我們可以使用 import 語(yǔ)法來(lái)導(dǎo)入類,以及使用 alias 設(shè)置別名:

    <data>
        <import type="java.lang.String"/>
        <import type="com.zhangke.demo.jetpack.entity.User"
                alias="ZKUser"/>
        <variable name="title" type="String" />
        <variable name="User" type="ZKUser" />
    </data>

我們也可以通過(guò) default 字段設(shè)置默認(rèn)值:

<TextView android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{title, default=my_default}"/>

綁定數(shù)據(jù)

我們?cè)傩陆ㄒ粋€(gè)類似上述示例代碼中的 DataBinding 布局文件 activity_main.xml 之后,Gradle 會(huì)根據(jù)布局創(chuàng)建一個(gè) ActivityMainBinding 類,我們需要獲取該對(duì)象來(lái)綁定數(shù)據(jù)。
使用 DataBinding 時(shí),我們不需要再按照之前的 setContentView 的方式來(lái)設(shè)置布局到 Activity 中,應(yīng)該通過(guò) DataBindingUtil#setContentView 來(lái)設(shè)置,該方法會(huì)返回對(duì)應(yīng)的 DataBinding 對(duì)象,例如我們創(chuàng)建的布局文件為 activity_main,那么生成的就是 ActivityMainBinding,我們可以通過(guò)在 data 節(jié)點(diǎn)使用 class 關(guān)鍵字更改這種默認(rèn)的名字:

<data class=".MainActivityBinding">
    …
</data>

前面的 . 號(hào)表示使用當(dāng)前包名,也可以使用全限定包名指定。
然后是獲取綁定類:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.lifecycleOwner = this
    binding.title = "Title"
}

我們先獲取 DataBinding 對(duì)象,然后設(shè)置 variable 數(shù)據(jù),lifecycleOwner 適用于管理生命周期的方法,設(shè)置后 Databinding 可以感知到 Activity 的生命周期,保證數(shù)據(jù)在可見時(shí)才會(huì)更新,不可見時(shí)不會(huì)更新數(shù)據(jù)。
如果是在 Fragment/ListView/RecyclerView 中,我們可以通過(guò)下面的方法獲取 DataBinding:

binding = ActivityMainBinding.inflate(layoutInflater, null, false)

綁定普通數(shù)據(jù)

DataBinding 可以綁定普通數(shù)據(jù)對(duì)象(非 Observable/LiveData),例如上述例子中綁定了一個(gè) String 類型的數(shù)據(jù)。綁定普通數(shù)據(jù)我們只需要按照上述的代碼設(shè)置即可。

綁定可觀察數(shù)據(jù)

綁定可觀察數(shù)據(jù)意味著當(dāng)數(shù)據(jù)變化時(shí) UI 會(huì)跟著一起變化,綁定可觀察數(shù)據(jù)有三種方式:objects、fields 和 collections.

對(duì)單個(gè)變量的綁定:fields

對(duì)于一些數(shù)據(jù)類,如果我們不想繼承 BaseObservable 或者只需要其中幾個(gè)字段支持可觀察,那么可以使用這種方式來(lái)創(chuàng)建可觀察數(shù)據(jù):

class User {
    var age = ObservableInt()
    var name = ObservableField<String>()
}

對(duì)于基本類型和 Parcelable 我們可以直接使用對(duì)應(yīng)的包裝類:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

引用類型使用帶有泛型參數(shù)的 ObservableField 類來(lái)創(chuàng)建:

var name = ObservableField<String>()

泛型參數(shù)為數(shù)據(jù)類型。

對(duì)集合的綁定:collections

我們同樣可以在布局中綁定集合中的某個(gè)元素,當(dāng)集合中的數(shù)據(jù)發(fā)生變化后會(huì)同步更新到 UI。

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="androidx.databinding.ObservableMap"/>
        <import type="androidx.databinding.ObservableList"/>
        <variable name="map" type="ObservableMap&lt;String, Object>"/>
        <variable name="list" type="ObservableList&lt;Object>"/>
    </data>
...
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(map.count)}" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(list[0])}" />
</layout>

綁定數(shù)據(jù)到 UI 中:

val map = ObservableArrayMap<String, Any>().apply { put("count", 0) }
binding.map = map
val list = ObservableArrayList<Any>().apply { add(0) }
binding.list = list

對(duì)于 List 來(lái)說(shuō),可以直接使用 [] 運(yùn)算符( list[0] )獲取對(duì)應(yīng)位置的元素。
而 Map 就很有趣了,可以使用 . 運(yùn)算符直接獲取 key 對(duì)應(yīng)的 value:map.count,這還是很有意義的,我們?nèi)绻幌攵x一個(gè)數(shù)據(jù)實(shí)體,可以直接使用 Map 來(lái)替代。

綁定對(duì)象:objects

這種是最常用的一種方式,需要綁定的數(shù)據(jù)實(shí)體類繼承 BaseObservable:

class Person : BaseObservable() {
    @get:Bindable
    var country: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.country)
        }
    
    @get:Bindable
    var sex: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.sex)
        }
}

首先要在需要支持可觀察的數(shù)據(jù)上添加 @get:Bindable 注解,然后重寫 set 方法,在其中調(diào)用 notifyPropertyChanged 方法表示更新該數(shù)據(jù),BR 是自動(dòng)生成的,包名跟當(dāng)前包名一致,會(huì)根據(jù) Bindable 注解的變量生成對(duì)應(yīng)的值;也可以調(diào)用 notifyChange() 方法更新所有數(shù)據(jù)。

綁定 LiveData

LiveData 目前也支持?jǐn)?shù)據(jù)綁定,綁定方式跟上述介紹的一樣:

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable name="desc"
            type="androidx.lifecycle.MutableLiveData&lt;String>" />
    </data>
...
    <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
           android:layout_gravity="center_vertical|end"
           android:textSize="18sp"
           android:text="@{desc}" />
</layout>

我們可以直接將 LiveData 賦值給 text,然后綁定數(shù)據(jù):

val desc = MutableLiveData<String>()
binding.desc = desc

需要注意的是,老版本的 Gradle 是不支持 LiveData 的綁定的,需要更新到 Gradle 3.4 及以上。
另外,使用 LiveData 有個(gè)顯示,更新 LiveData 數(shù)據(jù)時(shí)必須在主線程才行。

雙向綁定

上述的單向綁定是指數(shù)據(jù)變化后更新 UI,而雙向綁定是指其中任意一個(gè)變化后都會(huì)同步更新到另一個(gè)。
雙向綁定使用 @={} 表達(dá)式來(lái)實(shí)現(xiàn):

    <data>
    ...
        <variable
            name="input"
            type="androidx.databinding.ObservableField&lt;String>" />
    </data>
...
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={input}"/>

事件綁定

事件綁定其實(shí)跟數(shù)據(jù)綁定一樣,本質(zhì)上就是將監(jiān)聽器對(duì)象綁定到 UI 元素上:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
    <data class="">
    ...
        <variable
            name="handler"
            type="com.zhangke.demo.jetpack.MainActivity.EventHandler" />
    </data>
...
    <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_marginTop="10dp"
       android:onClick="@{handler::onToastBtnClick}"
       android:text="ToastClick"/>
</layout>

然后我們寫好監(jiān)聽事件,綁定到 binding 中即可:

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        ...
        binding.handler = EventHandler()
    }

    ...
    inner class EventHandler {
        fun onToastBtnClick(v: View) {
            Toast.makeText(this@MainActivity, "Click", Toast.LENGTH_SHORT)
                .show()
        }
    }
}

自定義參數(shù)綁定:BindingAdapter

目前已經(jīng)支持的雙向綁定的參數(shù)列表如下:


除了上述的參數(shù)外,我們也可以使用 BindingAdapter 創(chuàng)建自定義參數(shù)。

例如我們需要使用 Glide 加載網(wǎng)絡(luò)圖片,可以先創(chuàng)建一個(gè)使用了 BindingAdapter 注解的函數(shù),注解中的字段為參數(shù)名,函數(shù)的第一個(gè)參數(shù)必須為目標(biāo) View 或者其子類,因此使用 Kotlin 時(shí)我們可以定義為擴(kuò)展函數(shù),這樣使用很方便。

@BindingAdapter("imageUrl")
fun ImageView.loadImage(url: String) =
    Glide.with(this.context).load(url).into(this)

然后我們就可以在布局代碼中直接使用該參數(shù)加載圖片:

<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginTop="10dp"
    imageUrl="@{imageUrl}"
    android:layout_gravity="center_horizontal"/>

好了,DataBinding 差不多就講到這里了,上面的全部代碼已經(jīng)放到了我的 Github 上,需要的自取,歡迎 star~

https://github.com/0xZhangKe/JetpackDemo

如果覺得還不錯(cuò)的話,歡迎關(guān)注我的個(gè)人公眾號(hào),我會(huì)不定期發(fā)一些干貨文章~


公眾號(hào)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容