DataBinding使用介绍

11,543 阅读8分钟

一 概述

DataBinding 是2015年谷歌I/O大会上发布的一个数据绑定框架,也就是把数据绑定到UI上,DataBinding 可以让 Activity 和 Fragment 减少很多逻辑,使其更容易维护、方便。同时也能提高性能,避免内存泄漏 以及 空指针 异常 ,同时DataBinding也可以双向绑定,使UI的改变同时同步到数据上,DataBinding不是MVVM架构的必需品,但是官方已经为咱们提供了这个库,为啥我们不好好利用呢。

具体的demo地址github

二 DataBinding优缺点

2.1 优点

  1. 不用再 findViewById 了(当然kotlin也可以不用喽)
  2. 减少了 Avtivity和Fragment的逻辑处理,使Activity 和Fragment逻辑更加清晰,容易维护
  3. 提高性能,避免内存泄漏 以及 空指针
  4. 双向绑定,当View改变的时候会通知Model,当Model改变的时候会通知View

2.2 缺点

  1. 很难定位bug,当有个界面展示不对的时候,你不知道是View的问题,还是Model的问题,还是编写逻辑的问题,
  2. xml中 不能Debug
  3. 在xml中写代码,这个可能是我们Android开发有点反感的,同时xml里面是Java代码,不能使用kotlin的简洁代码
  4. 双向绑定技术,不利于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。支持的有三种

  1. Obssservable扩展的属性
  2. ViewModel + Obssservable扩展的属性,可以不用使用 binding.lifecycleOwner = this 绑定生命周期的方法,即可实现单向绑定
  3. 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 其他一些常见的引用

  1. 使用 一些类中的常量 ,比如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}"/>
  1. 使用工具类的静态方法,把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" />
  1. 引用资源 比如 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)
    }
}

BindingMethods 和 BindingConversion 基本上没用,暂时不要考虑用就行了

一般咱们自定义bindadapter的时候,xml是没有提示的,咱们可以在 attrs.xml 里面写入咱们的 bind,这样就有提示了