1.什么是数据绑定
DataBinding是在xml中以声明的方式,将布局中的UI组件绑定与数据源绑定。它可以引用类,并且使用里面的变量或方法,而且控件自身也可以和bean中的某个字段进行关联(包括单项关联或双向关联),这样做的好处是,当任何一方发生变化的时候,另一方会自动更新自己的值。
相比传统的findViewById()查找控件 然后再设置值。DataBinding就显得简洁很多,使得页面与布局之间的耦合度进一步降低,减少了重复的findViewById这一类的代码。
DataBinding具有如下几点优势:
- 项目更加简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成
- 不再需要findViewById()方法,大量减少了Activity中的代码
- 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互
- 可以提高应用性能,有利于防止内存泄漏以及避免发送Null指针异常
- 可以更好的实现MVVM架构
DataBinding 将 Layout布局文件中的UI组件与数据模型Model进行绑定:
- 当 用户 通过UI组件 修改数据时,会将数据自动更新到数据模型中
- 数据模型中的数据改变时,会自动更新到UI组件中。
2.基础使用:
DataBinding的绑定有两种方式,单向数据绑定和双向数据绑定:
- 单向数据绑定:例如,当Model中的数据改变时,通知改变页面上的值。
- 双向数据绑定: 当Model中的数据改变时,通知改变页面上的值。当页面上的数据修改之后,通知改变Model中的值。
需要用到的依赖:
在Model内的build.gradle中, android/defaultConfig配置块中,配置如下内容:
ini
android {
defaultConfig {
buildFeatures{
dataBinding = true
}
}
}
2.1单向绑定:
2.1.1:定义数据类(Model):
arduino
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2.1.2 布局转换
在build.gradle构建脚本中,设置dataBinding{enable = true}后,第一时间点击右上角的SyncNow同步代码,否则下面无法进行布局转换。
在布局文件中,将鼠标放在左上角第一个字符位置,按下 Alt+回车 组合键,

按下Alt + 回车 组合键,会弹出下面的菜单, 有 Convert to data binding layout 选项,选择该选项,就可以键布局文件切换成DataBinding布局文件
如果没有该选项,说明在build.gradle构建脚本中 设置了dataBinding{ eabled = true}后,没有同步代码:

转为DataBinding布局前的结构:
ini
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
转换为DataBinding布局后的结构:
xml
<?xml version="1.0" encoding="utf-8"?>
<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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2.1.3 在xml中定义data标签,设置数据类型信息
ini
<?xml version="1.0" encoding="utf-8"?>
<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="student"
type="com.wzj.databindingdemo.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4"
tools:text="Tom" />
<TextView
android:id="@+id/text_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(student.age)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_name"
app:layout_constraintVertical_bias="0.2"
tools:text="18" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在上述代码中,通过data标签,设置了数据类型信息,

在布局文件中,为组件设置tools:text属性,该属性只能在Design视图中查看,方便开发调试,不会显示在最终的应用中。
在布局文件中,使用@{student.变量名},获取该数据类型对象的成员,并设置到布局组件中

3.1.4 注意事项
需要注意的是,如果字段的类型是int类型,如student.age,设置 android:text属性时,需要将其转换为String类型
3.1.5 效果展示

2.2双向绑定:
双向绑定是建立在单向绑定的基础上,实际开发中用到双向绑定的地方没有单向绑定多。
例如EditText,用户编辑EditText,字段自动同步,当字段有变化时,EditText自动更新
2.2.1 代码示例:
定义一个Student类:
typescript
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getNameText(){
return "姓名"+name;
}
public String getAgeText(){
return "年龄"+age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2.2.2 通过BaseObservable实现双向绑定
在该类中设置了@Bindable注解,只要student对象中的name发生了变化,绑定的组件中的内容就会发生变化
如果要实现通过EditText修改数据模型的效果,需要再实现一个setStudentName()方法,该方法需要与之前使用@Bindable注解修饰的getStudentName()方法对应。
修改后需要调用notifyPropertyChanged(BR.xxx)通知数据模型进行变更 BR类是BaseObservable子类中由@Bindable注解修饰的方法生成的
BR类生成位置在:app\build\generated\ap_generated_sources\debug\out\com.databindingdemo\BR.java

2.2.3 布局文件设置(重点):
在EditText控件赋值时,需要使用 android:text="@={student.studentName}" 进行赋值,注意值为: @={student.studentName},比之前的数据绑定多了一个等号

2.2.4 在Activity中,
在Activity组件中,向布局中设置的对象类型是StudentViewModel类型的,不是Student类型的

2.2.5 执行结果:
逐个字母删除Tom,然后输入Jack

2.3 ObservableField实现数据模型Model与视图View双向绑定(优化上述例子)
在上面的做法中,存在一些弊端,首先我们的类必须继承自BaseObservable,另外,在Getter方法前还需要加上@Bindable标签,告诉DataBinding我们要绑定该字段,最后,还需要再Setter方法中手动调用notifyPropertyChanged()方法以通知观察者。
有一种更加简单的做法。那就是ObservableField,它能将普通对象包装成一个可观察的对象,ObservableField可以用于包装各种基本类型,集合数组,自定义类,实现代码如下:

上述代码,也能实现与BaseObservable双向绑定相同的效果。我们只通过ObservableField对象将数据给包装起来,并为字段编写了Getter和Setter方法。GetStudentName()方法在程序启动时被自动调用,当用户修改EditText内容时,setStudentName()方法被自动调用
DataBinding还提供了可观察集合ObservableArrayMap对象和ObservableArrayList对象
ObservableField与LiveData
我们发现ObservableField的使用方式和作用与LiveData很像。实际上,两者可以替换使用的,二者的区别在于,LiveData与生命周期相关,它通常在ViewModel中使用,并且需要在页面中通过observe()方法对变化进行监听。而双向绑定无需在页面中加入额外的代码,耦合度更低。
3.表达式语言
在布局文件中是通过 @{},语法写入控件中的,比如,TextView文本被设置为Student.的Name属性:
但是表达式的功能不止如此,我们还可以在表达式语言中使用以下运算符和关键字:
- 算数运算符 + - / * %
- 字符串连接运算符 +
- 逻辑运算符 && ||
- 二元运算符 & | ^
- 一元运算符 + - ! ~
- 位移运算符 >> >>> <<
- 比较运算符 == > < >= <= (注意,<需要转义为<)
- instanceof
- 分组运算符()
- 字面量运算符-字符,字符串,数字,null
- 类型转换
- 方法调用
- 字段访问
- 数组访问[]
- 三元运算符 age > 18 ? 未成年 : 已成年
3.字符串拼接函数
我们可以在数据类中定义字符串拼接函数,直接在DataBinding布局文件中,调用字符串拼接函数:
还是Student类, 在DataBinding布局文件中,声明

布局文件中的控件中:
- 调用@{student.nameText()} 设置 "姓名 : tom"
- 调用@{student.ageText()} 设置 "年龄 : 18"

4.绑定点击事件函数
首先在Student类中定义如下方法:
然后在xml的TextView的android:onClick属性中,设置@{student.onClick}点击事件

5.导入类(import) 变量,包含(使用include导入二级界面布局)
5.1 导入类 import
导入起始就跟使用类方法差不多,通过导入功能,可以在布局文件中引用类,就像在托管代码一样,可以在data元素下使用多个import元素,也可以不使用。
导入View类可以通过绑定表达式引用该类,如下图展示了如何引用View类的VISIBLE和GONE
注意类型别名
当类名有冲突的时候,其中一个类可以使用别名重命名.使用alias即可
当我们使用的时候,使用 myView 即可

导入其他类
导入的类型可用作变量和表达式中的类型引用。以下显示了用作变量类型的sutdent和List
ini
<import type="com.wzj.databindingdemo.Student"/>
<import type="java.util.List"/>
<variable name="student" type="Student" />
<variable name="studentList" type="List<Student" />
5.2 变量
变量就是我们使用的标签引入的属性,变量类型会在编译时进行检查。
ini
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
5.3 使用include导入二级界面布局
对于include进行引用的布局文件,我们称为二级页面。DataBinding也支持从一级页面将数据传递到二级页面.

一级页面传递数据 A.xml

二级页面接收数据 B.xml

6.自定义BindingAdapter
借助@BindingAdapter注解,可以将自定义逻辑绑定到DataBinding布局中
例如:为ImageView控件绑定数据,传入一个url网络图片地址,该控件中显示网络图片,如果网络图片加载失败或者为空,则加载默认的本地资源。该操作必须自定义一段代码逻辑进行实现,使用简单的数据绑定无法实现该功能。
6.1 基础使用
首先编写处理图片的BindingAdapter类,在该方法中通过Glide加载网络图片

需要注意的是:ImageViewBindingAdapter中的方法均为静态方法。第一个参数为调用者本身(也就是当前的UI控件),第二个参数是布局文件在调用该方法时传递过来的参数。我们在@BindingAdapter中添加的一个别名image,布局文件正是通过别名来调用该方法的。
在布局文件中调用BindingAdapter
首先,需要在布局变量中定义一个String,用于存放网络图片的地址,然后再ImageView通过别名,及我们再ImageViewBindingAdapter文件中定义好的别名image,来调用静态方法,布局表达式@{}中的参数,则是调用方法时传入的参数
需要注意,我们需要在布局文件最外层包含以下命名空间,才能调用自定义@BindingAdapter标签定义的静态方法:

在Activity中为布局变量赋值,也就是networkImage

结果如下:

6.2 方法重载
刚才我们已经自定义BindingAdapter类,让UI空间能够通过属性设置,来加载网络图片。现在,我们可以通过方法重载,让该静态方法支持显示项目资源文件中的图片,代码如下:
布局文件中:
在Activity中:

这样,BindingAdapter就能够显示项目资源文件中的图片了

6.3 多参数重载
我们可以将上面的两个方法,合并成一个方法,并且将两个参数同时传入方法中,当网络图片地址为空时,则显示imageResource参数所指定的图片:
在@BindingAdapter标签中,方法的别名设置以value={"",""}的形式设置,在该方法中,我设置了两个别名,及通过这两个别名都可以调用该静态方法,变量requireAll用于告诉DataBinding库这些参数是否都要赋值,默认为true即要全部赋值.
布局代码如下:

当networkImage为空时,ImageView会显示localImage所指定的图片.
方法是如何区分传入的资源是网络资源还是本地资源呢?Databinding是根据传递参数的类型来进行区分,比如说: networkImage是网络图片资源,那么就是String类型,方法就知道它传入的是网络资源图片,localImage是本地类型资源,那么就是int类型,方法就知道它传入的是本地资源图片。
6.4 可选旧值
BindingAdapter可以覆盖Android原先的控件属性。比如说,可以设置在每一个TextView的文本都加上后缀: -哈哈哈,代码如下:
xml中:
activity中:
实现效果:

6.5 对象转换
6.5.1 自动转换对象
6.5.2 自定义转换BindingConversion
与BindingAdapter类似 以下方法会将布局文件中所有以@{String},方式引用到的String变量加上后缀 Android

xml文件中的TextView:
最终效果:
注意:@BindingConversion会将布局文件中所有以@{String}方法引用到的String变量加上后缀 Android,但是分以下两种情况:
- 如果属性是Android自带的属性。比如说android:text,@BindingAdapter的优先级低于@BindingConversion,会优先调用@BindingConversion注解的方法
- 如果属性是自定义的。比如我们上面例子中的图片加载,image别名,这时候@BindingAdapter的优先级会高于@BindingConversion,会优先调用@BindingAdapter注解的方法。
7.DataBinding 在 RecyclerView中使用
7.1定义数据类Student
arduino
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
7.2 定义item布局
ini
<?xml version="1.0" encoding="utf-8"?>
<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="student"
type="com.wzj.databindingdemo.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="50dip">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name}"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Tom" />
<TextView
android:id="@+id/age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(student.age)}"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="18" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
7.3 定义适配器Adapter
在DataBinding布局中,绑定了 type="com.wzj.databindingdemo.Student" 类型的数据
数据绑定布局ItemBinding在自定义的ViewHolder类中维护,可以通过该类获取数据绑定布局,并为其设置数据
在onCreateViewHolder中,获取DataBinding布局,并将其设置给自定的ViewHolder
获取DataBinding布局,调用DataBindingUtil.inflate函数,获取item.xml对应的DataBinding布局文件类ItemBinding实例对象。
在onBindingViewHolder中
scala
public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {
private List<Student> list = Arrays.asList(
new Student("Tom", 18),
new Student("Jerry", 12),
new Student("Mickey", 16),
new Student("Donald", 14)
);
//在onCrateViewHolder中,调用DataBindingUtil.inflate函数,获取rv_item.xml对应的DataBinding布局文件类,并设置给ViewHolder
@NonNull
@Override
public StudentAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
RvItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.rv_item, parent, false);
return new ViewHolder(itemBinding);
}
//在onBindViewHolder函数中,为每个列表条目设置数据
@Override
public void onBindViewHolder(@NonNull StudentAdapter.ViewHolder holder, int position) {
holder.itemBinding.setStudent(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
//在自定义的ViewHolder类中,维护了rv_item.xml布局文件对应的RvItemBinding数据绑定类实例对象,在构造函数中设置该itemBinding对象
public class ViewHolder extends RecyclerView.ViewHolder {
private RvItemBinding itemBinding;
public ViewHolder(View itemView) {
super(itemView);
}
public ViewHolder(RvItemBinding itemBinding) {
super(itemBinding.getRoot());
this.itemBinding = itemBinding;
}
}
}
7.4 activity_main.xml

7.5 Activity中

运行效果:
