DataBinding 浅析

Databinding是什么?

官网中描述他是

将布局中的界面组件绑定到应用中的数据源

等等。这不就是findViewById 嘛。 eumm. 没错.DataBinding 可以替代掉 findViewById, eumm?, 那干嘛不用ViewBinding, 如果仅仅从官网的示例上去看。好像 DataBinding 干的事情和 ViewBinding是一样的。都是为了消灭findViewById。但是DataBinding 的强大不仅仅是替换 findViewById

配置DataBinding

DataBinding 需要在 Android Studio 3.6 Canary 11 及更高版本中可用。配置

1
2
3
4
5
6
android {
...
dataBinding {
enabled = true
}
}

从Android Gradle Plugin 4.0.0-alpha05这里开始,有一个称为buildFeatures启用构建功能的新块。需要在buildFeatures配置

1
2
3
4
buildFeatures {
// 启用 DataBinding
dataBinding = true
}

👆 注意。需要在 主module 和 子module 都需要添加。笔者使用的是 4.1.1 版本的 gradle

布局绑定

配置完成DataBinding以后。在新建的 xml中。找到根布局。将xml转变成 data binding layout如下所示

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linear"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

</LinearLayout>

转变完成以后。会生成一个新的布局。如下所示。以 layout 的根标记开始,多了一个 data 然后是我们熟悉的根布局。
每一个databinding 都会生成一个 Binding 类。 类名默认是 xml文件名的驼峰之后加一个Binding字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>

</data>

<LinearLayout
android:id="@+id/linear"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

</LinearLayout>
</layout>

在Activity中使用 DataBindingUtil 去获取 DataBinding为我们生成的 Binding 类。使用 Binding类。获取 xml 文件中的 id 即可使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DataBindAct : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//之前的样子
//setContentView(R.layout.activity_databinding)
//现在的样子
var bind = DataBindingUtil.setContentView<ActivityDatabindingBinding>(
this,
R.layout.activity_databinding
)

//通过id 即可获取
bind.linear.addView(MaterialButton(this).apply {
text = "Btn"
})
}
}

如果是在Fragment中 使用 inflate 方法即可

1
val dataBind = DataBindingUtil.inflate(inflater, R.layout.fragment_databinding, container, false)

到这里DataBinding 的作用和 ViewBinding 是一样的。都是替换了 findViewById. 接下来看一些不一样的。也是其真正强大的地方

入门使用

先设置一个简单的。将数据通过DataBinding绑定起来

修改xml 在 data节点中 添加一个节点 variable,设置nametype,其中type是一个自己定义的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>
<variable
name="info"
type="com.starot.model_jetpack.databinding.User" />
</data>

<LinearLayout
...>
<com.google.android.material.button.MaterialButton
android:text="@{info.name}"
android:id="@+id/btn_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>

进行绑定

1
2
3
4
5
6
7
8
9
10
11
class DataBindAct : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bind = DataBindingUtil.setContentView<ActivityDatabindingBinding>(
this,
R.layout.activity_databinding
)
//通过👇的方法就完成了绑定。
bind.info = User("allens",18)
}
}

比较有意思的是 android:text="@{info.name}" 这个,在之前的xml 中从来没有这样的写法。这个也是 DataBinding提供的绑定表达式,除了基础的,还可以进行运算。例如 android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"等功能. 各种花里花哨的.

关于绑定表达式,更多信息可参考官网

结合LiveData使用

不出意外使用DataBinding的开发者,一定是想着使用MVVM框架。在实际使用中。我们更多的是将信息存放在ViewModel中。那么 DataBinding 又是如何与 ViewModel 配套使用呢

首先创建ViewModel,这里我继承了LifecycleObserver 让让ViewModel获取生命周期感知能力,实际中使用 DefaultLifecycleObserver 个人认为更佳合适。不过DefaultLifecycleObserver需要单独引用依赖。这里演示就使用LifecycleObserver

1
2
3
4
//继承LifecycleObserver 让ViewModel获取生命周期感知能力
class MyViewModel : ViewModel(), LifecycleObserver {
val user = MutableLiveData<User>()
}

将创建的ViewModel 声明在xml 布局当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.starot.model_jetpack.databinding.MyViewModel" />
</data>

<LinearLayout
...>

<com.google.android.material.button.MaterialButton
android:id="@+id/btn_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.user.name}" />
</LinearLayout>
</layout>

创建ViewModel 以及注册到LifeCycle 不是本文重点。重点看一下 加入 👇 手势的地方。

👇建立绑定关系

这个是和上面的简单使用一个道理。主要就是容易忘记

👇用LiveData配合DataBinding的话,要手动将生成的Binding布局类和LifecycleOwner关联起来

这一行也非常重要.如果不加的话.屁效果都没有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DataBindAct : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//创建DataBinding
val bind = DataBindingUtil.setContentView<ActivityDatabindingBinding>(
this,
R.layout.activity_databinding
)
//创建ViewModel
val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
//绑定生命周期
lifecycle.addObserver(viewModel)
//👇建立绑定关系
bind.viewModel = viewModel
//👇用LiveData配合DataBinding的话,要手动将生成的Binding布局类和LifecycleOwner关联起来
bind.lifecycleOwner = this
}
}

BindAdapter

回到我们刚刚看到的表达式android:text="@{info.name}",凭啥他就可以直接绑定?

其实在DataBinding中有个叫做 绑定适配器的东西。他负责了表达式匹配。匹配上以后就会调用其方法。

DataBinding 给我们提供了很多适配器

我们了解一下 android:text="@{info.name}" 背后的故事。找到TextViewBindingAdapter在其内部能够发现 @BindingAdapter的注解 如下所示。@BindingAdapter("android:text") 当标记@BindingAdapter的注解匹配到其内部的值时候(“android:text”)。会执行下面的方法进行设置。

除了默认提供的。我们也可以仿造其写法自定义一些适配器.例如图片适配器 ImageViewAdapter

1
2
3
4
5
6
7
8
9
10
11
12
class ImageViewAdapter {
companion object{
//👇需要注意 一定要是静态方法
@JvmStatic
@BindingAdapter("imageUrl")
fun load(image: ImageView, url: String?) {
if (url == null) return
//使用coil图片加载框架
image.load(url)
}
}
}

相比这种方式.笔者更佳推荐使用kotlin的拓展函数去实现。这种方式。如果想在代码中使用一定是ImageViewAdapter.load(image,url) 有点啰嗦

看一下使用拓展函数如何实现。

1
2
3
4
5
6
@BindingAdapter("imageUrl")
fun ImageView.load(url:String?){
if (url == null) return
//使用coil图片加载框架
this.load(url)
}

这么写 以后想在代码中使用。就简单多了image.load(url)即可

当然,图片加载,我们需要的不仅仅是URL 还有错误和占位图。BindingAdapter很人性的给我们提供了更多的选择,

requireAll false 设置了任意属性时调用适配器

1
2
3
4
5
6
7
8
9
@BindingAdapter(value = ["android:imageUrl", "android:placeholder", "android:error"], requireAll = false)
fun ImageView.load(url: String?, placeholder: Drawable?, error: Drawable?) {
if (url == null) return
//使用coil图片加载框架
this.load(url) {
placeholder(placeholder)
error(error)
}
}

双向数据绑定

先上一个示例。注意看 checked 的表达式中 @= 表示双向数据绑定。

  • 当UI 发生变化的时候。会修改viewModel.check 这个变量的内存.
  • 当`viewModel.check`` 这个变量的内存发生变化时候. UI 也会相应变化
1
2
3
4
<androidx.appcompat.widget.AppCompatCheckBox
android:checked="@={viewModel.check}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

参考

【背上Jetpack之DataBinding】数据驱动魔法师 何时迎来翻身日?