DataBinding使用介绍
启用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"/>