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<String, Object>"/>
<variable name="list" type="ObservableList<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<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<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ā)一些干貨文章~
