啟用DataBinding
首先設置使用DataBinding,在app module的build.gradle中添加如下代碼即可:
android{
...
dataBinding{
enabled = true;
}
}
布局綁定
在使用DataBinding時就不能按照之前的方式來編寫布局文件了,布局文件的根布局應該是layout,layout中同時存放要綁定的數據及布局,如下:
<?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節點中存放數據,下面就是常見的布局文件。data中的variable標簽為變量,類似於我們定義了一個變量,name為變量名,type為變量全限定類型名,包括包名。布局中通過@{}來引用這個變量的值,{}中可以是任意java表達式,但不推薦使用過多代碼。
我們可以使用import語法來導入類,以及使用alias設置別名:
<data>
<import type="java.lang.String"/>
<import type="包名.User" alias="ExUser"/>
<variable name="title" type="String"/>
<variable name="User" type="ExUser"/>
</data>
也可以使用default字段設置默認值:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{title, default=my_default}"/>
綁定數據
在新建一個類似上述實例代碼中的DataBinding布局文件activity_main.xml之后,Gradle會根據布局創建一個ActivityMainBinding類,我們需要獲取該對象來綁定數據,使用DataBinding時,不需要再按照之前的setContentView的方式來設置布局到Activity中,應該通過DataBindingUtil#setContentView來設置,該方法會返回對應的DataBinding對象,例如我們創建的布局文件為activity_main,那么生成的就是ActivityMainBinding,我們可以通過data節點使用class關鍵字改變這種默認的名字:
<data class=".MainActivityBinding">
...
</data/
"."號表示當前包名,也可以使用全限定包名指定。然后是獲取綁定類:
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.title = "Title"
}
我們先獲取 DataBinding 對象,然后設置 variable 數據,lifecycleOwner 適用於管理生命周期的方法,設置后 Databinding 可以感知到 Activity 的生命周期,保證數據在可見時才會更新,不可見時不會更新數據。
如果是在 Fragment/ListView/RecyclerView 中,我們可以通過下面的方法獲取 DataBinding:
binding = ActivityMainBinding.inflate(layoutInflater, null, false)
綁定普通數據
DataBinding可以綁定普通數據對象(非Observable/LiveData),例如上述例子中綁定了一個String類型的數據。綁定普通數據我們只需要按照上述的代碼設置即可。
綁定可觀察數據
綁定可觀察數據意味着當數據變化時UI會一起變化,綁定可觀察數據由三種方式:object,field,collections。
對單個變量的綁定:fields
對於一些數據類,如果我們不想繼承BaseObservable或者只需要其中幾個字段可觀察,那么可以使用這種方式來創建可觀察數據:
class User{
var age = ObservableInt()
var name = ObservableField<String>()
}
對於基本類型和Parcelable我們可以直接使用對應的包裝類:
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableShort
- ObservableInt
- ObservableLong
- ObservableFloat
- ObservableDouble
- ObservableParcelable
引用類型使用帶有泛型參數的ObservableField類來創建
var name = ObservableField<String>()
泛型參數為數據類型
對集合的綁定:collections
我們同樣可以在布局中綁定集合中的某個元素,當集合中的數據發生變化后會同步更新到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>
綁定數據到UI中
val map = ObservableArrayMap<String, Any>.apply(){put("count", 0)}
binding.map = map
val list = ObservableArrayList<Any>().apply{add(0)}
binding.list = list
對於List來說,可以直接使用[]運算符(list[0])獲取對應位置的元素。而Map就很有趣了,可以使用.運算符直接獲取key對應的value:map.count,這還是很有意義的,我們如果不想定義一個數據實體,可以直接使用map替代。
綁定對象:objects
這是最常用的一種方式,需要綁定的數據實體類繼承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
notifyPropertyChanger(BR.sex)
}
}
首先在需要支持可觀察的數據上添加@get:Bindable注解,然后重寫set方法,在其中調用notifyPropertyChanged方法表示更新該數據,BR是自動生成的,包名跟當前報名一致,會根據Bindable注解的變量生成對應的值;也可以調用notifyChange()方法更新所有數據。
綁定LiveData
LiveData目前也支持數據綁定,綁定方式跟上述介紹的一樣:
<?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,然后綁定數據:
val desc = MutableLiveData<String>()
binding.desc = desc
需要注意的是,老版本的Gradle是不支持LiveData的綁定的,需要更新到Gradle3.4及以上。另外使用LiveData有個顯示,更新到LiveData數據時必須在主線程才行。
雙向綁定
上述的單向綁定是數據變化后更新UI,而雙向綁定是指其中任意一個變化后都會同步更新到另一個。雙向綁定使用@={}表達式來實現:
<data>
...
<variable name="input" type="androidx.databinding.ObservableField<String"/>
</data>
...
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={input}"/>
事件綁定
事件綁定其實跟數據綁定一樣,本質上就是將監聽器對象綁定到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.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>
然后我們寫好監聽事件,綁定到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()
}
}
}
自定義參數綁定: BindingAdapter
目前已支持的雙向綁定的列表如下:
除了上述的參數外,我們也可以使用BindingAdapter創建自定義參數
例如我們需要使用Glide加載網絡圖片,可以先創建一個使用了BindingAdapter注解的函數,注解中的字段為參數名,函數的第一個參數必須為目標View或者其子類,因此使用Kotlin時我們可以定義為擴展函數,這樣使用很方便。
@BindingAdapter("imageUrl")
fun ImageView.loadImage(url: String)= Glide.with(this.context).load(url).into(this)
然后我們就可以在布局代碼中直接使用該參數加載圖片
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="10dp"
imageUrl="@{imageUrl}"
android:layout_gravity="center_horizontal"/>