一 概述
DataBinding 是2015年谷歌I/O大会上发布的一个数据绑定框架,也就是把数据绑定到UI上,DataBinding 可以让 Activity 和 Fragment 减少很多逻辑,使其更容易维护、方便。同时也能提高性能,避免内存泄漏 以及 空指针 异常 ,同时DataBinding也可以双向绑定,使UI的改变同时同步到数据上,DataBinding不是MVVM架构的必需品,但是官方已经为咱们提供了这个库,为啥我们不好好利用呢。
具体的demo地址github
二 DataBinding优缺点
2.1 优点
- 不用再 findViewById 了(当然kotlin也可以不用喽)
- 减少了 Avtivity和Fragment的逻辑处理,使Activity 和Fragment逻辑更加清晰,容易维护
- 提高性能,避免内存泄漏 以及 空指针
- 双向绑定,当View改变的时候会通知Model,当Model改变的时候会通知View
2.2 缺点
- 很难定位bug,当有个界面展示不对的时候,你不知道是View的问题,还是Model的问题,还是编写逻辑的问题,
- xml中 不能Debug
- 在xml中写代码,这个可能是我们Android开发有点反感的,同时xml里面是Java代码,不能使用kotlin的简洁代码
- 双向绑定技术,不利于View的复用,因为一个xml 里面绑定的一个Model,有可能另一个界面Model就不一样了,所以无法复用了。除非你再手动转一下这个Model
三 DataBinding使用
3.1 引入
在app的build.gradle中加上以下代码即可,不用引用其他的依赖
android {
...
dataBinding {
enabled = true
}
然后打开布局的xml,在根节点 mac按住command+return ,windows 按住 art+enter 就会出现这样的标记,选第一个就会出现DataBinding的layout。如果没有这个选项,重启studio就可以了,就会生成 layout的跟标签,data标签下面就是咱们要绑定的实体类,比如一个Cat类
<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="cat"
type="com.nzy.mvvmsimple.databinding.model.Cat" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_name_ob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{cat.name}"
android:textSize="20sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
然后在Activity中通过 DataBindingUtil 拿到刚才的 xml的bind类,生成此 bind 类默认是 xml文件名的驼峰之后加一个Binding字符串,比如 xml 是 activity_binddata,那么生成的类是 ActivityBinddataBinding,这个类名也可以改,但是基本上都是用默认的、
var binding:ActivityBinddataBinding =
DataBindingUtil.setContentView<ActivityBinddataBinding>(this, R.layout.activity_binddata)
var cat = Cat("咖啡猫")
binding.cat = cat
在 Fragment 或者 RecyclerView中 使用
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
在xml中 使用@{cat.name} 就是把Cat的name绑定在 了这个Textview上
<TextView
android:text="@{cat.name}"
android:textSize="20sp"
/>
原理就是在 TextViewBindingAdapter里面有个注解 上有这样的方法,最终调用的是这里,这里是通过注解设置的文本
3.2 data中标签的含义
variable里面就是一些需要绑定的Model变量
- type 是要使用的Model类(全限定名字),直接打名字会自动提示出来的
- name 就是在本xml 对于这个type的一个使用名字
- import 相当于咱们android中的导包,当一个Model有多个引用到这个Xml中时用这样的方式,比如下面User,有两个地方在使用,所以直接导进来使用即可,此时的type直接就是类名字,不是全限定名了
- 默认 java.lang.*包下的类会自动导入,所以此时type 不用写全限定名字
<data>
<variable
name="cat"
type="com.nzy.mvvmsimple.databinding.model.Cat" />
<import type="com.nzy.mvvmsimple.user.User" />
<variable
name="user1"
type="User" />
<variable
name="user2"
type="User" />
</data>
- alias 指定别名,也就是当用到的Model名字一样的时候,可以取一个别名
<data>
<import type="com.nzy.mvvmsimple.user.User" />
<import
alias="User2"
type="com.nzy.mvvmsimple.databinding.model.User" />
<variable
name="user1"
type="User" />
<variable
name="user2"
type="User2" />
</data>
3.3 单向绑定
单向绑定是指数据源改变之后会立马通知xml进行赋值改变,刷新UI。支持的有三种
- Obssservable扩展的属性
- ViewModel + Obssservable扩展的属性,可以不用使用 binding.lifecycleOwner = this 绑定生命周期的方法,即可实现单向绑定
- ViewModel + LiveData 必须使用 binding.lifecycleOwner = this 方法绑定生命周期 去 实现单向绑定
3.1 Observable扩展的属性
在创建实现 Observable 接口的类时要完成一些操作,但如果您的类只有少数几个属性,这样操作的意义不大。在这种情况下,您可以使用通用 Observable 类和以下特定于基元的类,将字段设为可观察字段,以及 ObservableField 属性
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableShort
- ObservableInt
- ObservableLong
- ObservableFloat
- ObservableDouble
- ObservableParcelable
例子
class Cat {
// 猫的名字用 ObservableField 包裹
var name: ObservableField<String> = ObservableField<String>()
// 是否显示猫的名字 用 ObservableBoolean
var isShowName = ObservableBoolean()
}
xml中因为引用到 View.VISIBLE,所以得导进来 View
<variable
name="cat"
type="com.nzy.mvvmsimple.databinding.model.Cat" />
<import type="android.view.View" />
...
<TextView
android:id="@+id/tv_name_ob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{cat.name}"
android:textSize="20sp"
android:visibility="@{cat.isShowName()?View.VISIBLE:View.INVISIBLE}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_name_view_model" />
绑定数据的Activity,这样就可以实现单向绑定了,UI会随着数据源的改变而改变
var cat = Cat()
cat.name.set("咖啡猫")
cat.isShowName.set(true)
binding.cat = cat
// 点击事件
bt_change.setOnClickListener {
cat.name.set("ObservableField 改变的咖啡猫")
cat.isShowName.set(!cat.isShowName.get())
}
3.2 ViewModel + Obssservable扩展的属性
其实这个相当于 Obssservable扩展的属性 方式,只是Cat类 变更成 ViewModel而已。
3.3 ViewModel + LiveData
这个是用的最多的一个,也就是在咱们android开发中。按道理来说,xml中避免引用过多的数据,一般引用一个ViewModel即可完成大部分的需求。 还是先看一下xml
<variable
name="viewmodel"
type="com.nzy.mvvmsimple.databinding.DataViewModel" />
...
<TextView
android:id="@+id/tv_name_view_model"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.nameLiveData}"
android:textSize="20sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_name" />
然后看看ViewModel,就是一个用LiveData包裹起来
class DataViewModel :ViewModel(){
var nameLiveData = MutableLiveData<String>()
}
然后看一下Activity的代码
var binding: ActivityBinddataBinding =
DataBindingUtil.setContentView<ActivityBinddataBinding>(
this,
R.layout.activity_binddata
)
val viewModel: DataViewModel by viewModels()
viewModel.nameLiveData.value = "小小猫"
// 绑定ViewModel
binding.viewmodel = viewModel
// 必须绑定生命周期,否则无效果
binding.lifecycleOwner = this
3.4 双向绑定
双向绑定是 当Model改变的时候,UI跟着改变,当UI改变的时候 Model也会跟着改变,比如EditText,CheckBox的状态,如果自定义双向绑定的时候,要注意判断状态是否需要改变,切记不要变成递归改变,死循环了。
举个例子
比如xml 里面 Textview 和 EditText 用的是一个Model的 nameLiveData ,此时你会看出来,TextView单向绑定,EditText双向绑定,当输入内容的时候 TextView也会改变
<variable
name="viewmodel"
type="com.nzy.mvvmsimple.databinding.DataViewModel" />
<TextView
android:id="@+id/tv_name_view_model"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@{viewmodel.nameLiveData}"
android:textSize="20sp"
/>
<EditText
android:id="@+id/et_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@={viewmodel.nameLiveData}"
android:textSize="20sp"
/>
android官方支持双向绑定的控件
3.5 点击事件绑定
3.5.1 直接传过来的click事件
<variable
name="click"
type="android.view.View.OnClickListener" />
...
<Button
android:onClick="@{click}"
android:id="@+id/bt1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定1" />
3.5.2. 通过ViewModel(或者其他类)的方法,不带参数
<Button
android:onClick="@{()->viewmodel.click()}"
android:id="@+id/bt2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定2" />
在ViewModel中
fun click() {
Toast.makeText(getApplication(), "绑定方式2", Toast.LENGTH_SHORT).show()
}
3.5.3. 通过ViewModel(或者其他类)的方法,带View本身的参数
<Button
android:onClick="@{(view)->viewmodel.click(view)}"
android:id="@+id/bt3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定3" />
ViewModel中的方法
fun click(view: View) {
Toast.makeText(getApplication(), "绑定方式3", Toast.LENGTH_SHORT).show()
}
3.5.4. 通过ViewModel(或者其他类)的方法,带其他的参数 不带View本身的参数
<Button
android:onClick="@{()->viewmodel.click(viewmodel.nameLiveData)}"
android:id="@+id/bt4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定4" />
ViewModel里面
fun click(name:String){
Toast.makeText(getApplication(), "绑定方式4$name", Toast.LENGTH_SHORT).show()
}
3.5.5. 通过ViewModel(或者其他类)的方法,带其他的参数 且带View本身的参数
<Button
android:onClick="@{(view)->viewmodel.click(view,viewmodel.nameLiveData)}"
android:id="@+id/bt5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定5" />
ViewModel里面,方法的顺序不能变
fun click(view:View,name:String){
Toast.makeText(getApplication(), "绑定方式5$name", Toast.LENGTH_SHORT).show()
}
3.5.6. 直接调用某个类的方法,viewmodel::click,此时的ViewModel方法是必须带View本身的参数的
<Button
android:onClick="@{viewmodel::click}"
android:id="@+id/bt6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定6" />
ViewModel里面,方法必须带View本身的参数
fun click(view: View) {
Toast.makeText(getApplication(), "绑定方式3", Toast.LENGTH_SHORT).show()
}
3.5.6. ObservableField<View.OnClickListener> 通过ObservableField包裹一个OnClickListener
<Button
android:onClick="@{viewmodel.observableFieldClick}"
android:id="@+id/bt7"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="绑定7" />
Actvity 里面设置给
viewModel.observableFieldClick.set(object :View.OnClickListener{
override fun onClick(v: View?) {
Toast.makeText(this@BindingActivity, "绑定方式7", Toast.LENGTH_SHORT).show()
}
})
3.6 xml中尽量用简单的一些表达式
- 算术运算符 + - / * %
- 字符串连接运算符 +
- 逻辑运算符 && ||
- 二元运算符 & | ^
- 一元运算符 + - ! ~
- 移位运算符 >> >>> <<
- 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
- instanceof
- 分组运算符 ()
- 字面量运算符 - 字符、字符串、数字、null
- 类型转换
- 方法调用
- 字段访问
- 数组访问 []
- 三元运算符 ?: 比如
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:text="@{user.displayName ?? user.lastName}"
相当于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
3.7 其他一些常见的引用
- 使用 一些类中的常量 ,比如View.VISEBLE 和 View.GONE
<data>
<import type="android.view.View"/>
</data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewmodel.isVisible ? View.VISIBLE : View.GONE}"/>
- 使用工具类的静态方法,把util导入进来
<import type="com.nzy.mvvmsimple.databinding.util.StringUtil" />
<Button
android:visibility="@{StringUtil.isEmpty(viewmodel.nameLiveData)?View.VISIBLE:View.GONE}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:text="button1" />
- 引用资源 比如 dimen ,同理drawable也是
android:padding="@{viewmodel.isBigPadding? @dimen/big_padding : @dimen/small_padding}"
或者是
android:padding="@{viewmodel.padding}" // 这个设置的是px。
android:src="@{viewmodel.isBigPadding? @drawable/hashiqi_one: @drawable/hashiqi_two}"
或者
android:src="@{viewmodel.drawable}"
3.8 自定义绑定适配器
使用kotlin定义适配器 需要在 app的buid.gradle 中 加入 apply plugin: 'kotlin-kapt' 这个插件
BindingAdapter
绑定一些自定义逻辑,使用 BindingAdapter 注释的静态绑定适配器方法支持自定义特性 比如自定义一个加载网络的ImageView,只有一个参数
@BindingAdapter("imageUrl")
fun setImageUrl(imageView: ImageView, url: String?) {
if (url != null) {
Glide.with(imageView).load(url).into(imageView)
}
}
比如定义一个有error的,有展位图的图片ImageView,requireAll==false 证明可以不用写全部
@BindingAdapter(value = ["imageUrl", "placeholder", "error"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?, error: Drawable?) {
if (url == null) {
imageView.setImageDrawable(placeHolder);
} else {
Glide.with(imageView).load(url).placeholder(placeHolder).error(error).into(imageView)
}
}