知是行之始,行是知之成。
文章配套的 Demo:https://github.com/muyi-yang/DataBindingDemo
Demo 支持 Java 和 Kotlin 双语言,master 分支为 Java 语言代码,kotlin 分支为 Kotlin 语言代码。
DataBinding介绍
2015 年的 Google IO 大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),它是为了解决数据和 UI 的绑定问题,同时也是对 MVVM 模型的一个实践和引领。MVVM 模型不了解的请自行补上。
优点
- 在 XML中绑定数据,XML变成UI的唯一真实来源
- 去掉 Activities & Fragments 内的大部分 UI 代码(setOnClickListener, setText, findViewById)
- 数据变化可自动刷新 UI,同时保证 UI 操作都在主线程运行
缺点
- IDE 支持还不完善,在 XML 中代码提示、表达式验证都有很大缺失
- 有些报错信息不明显,初学者排错难度较大
- 重构支持较差,数据绑定写在 XML 中,丧失面向对象特性
开启 DataBinding
Gradle 配置
想在你的应用程序中使用 Data Binding,需要在应用程序 module 中的 build.gradle 文件中添加 dataBinding 配置,此配置将会在你的项目里添加必要的 Data Binding 插件以及编译配置依赖。
android {
....
dataBinding {
enabled = true
}
}
注意:你必须为依赖于使用 Data Binding 的库(aar)的应用程序在 gradle 中增加开启 Data Binding 的配置,即使这个 module 没有直接使用 Data Binding 也需要。
例如: A module 依赖 B module,B module 又依赖 C module,但只有 C module 中使用了 Data Binding ,这个时候 A B C 三个 module 都必须在 gradle 中增加以上配置,否则会在 Data Binding V1 编译器中编译不过,在 V2 编译器中可编译,但会运行时出错。
Android Gradle 插件在版本 3.1.0-alpha06 包含一个新的 Data Binding 编译器(V2),用于生成 Binding 类。它的主要改变有:
- 新的编译器是增量编译 Binding 类,这在大多数情况下加快了编译速度。
- library 模块的 Binding 类会被编译并打包到 AAR 文件中。 依赖这些库的应用程序不再需要重新生成 Binding 类。
- 老版本在编译出错时,经常会出现一些与真真错误不符合的提示,这个问题在新版本中已经做了修改。
- 绑定适配器(binding adapters)只影响自己 module 中的代码和 module 的使用者,它不能更改 module 依赖库中的适配器的行为。( 绑定适配器后面我们会详细讲解)
要启用新的 Data Binding 编译器,请在 gradle.properties 文件中添加以下选项:
android.databinding.enableV2=true
或者在 gradle 命令中通过添加以下参数来启用新的编译器:
-Pandroid.databinding.enableV2=true
但是这个编译器在 Android Studio 3.2 版本中已经是默认启用的状态,所以你如果是 Android Studio 3.2 版本及以上版本可以不用关心这个特征。
注明:Data Binding 提供了兼容,它可以支持 Android 4.0(API 14)及以上系统。Data Binding 插件需要 Android Studio 1.3.0 及以上版本,Gradle 1.5.0 及以上版本才能正常工作。
本文章及例子是基于 Android Studio 3.3.1版本,Gradle 4.10.1 版本。电脑系统是 Ubuntu 16.04(有些问题跟系统有关)。
XML写法
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.constraint.ConstraintLayout 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">
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
Data Binding 的布局文件有点不同的是:起始根标签是 layout,标签里面的内容和普通的布局没有区别。
Data Binding 插件会检索所有布局文件,会把根标签为 layout 的布局编译出一个继承自 ViewDataBinding 的类(build 目录下)。其命名规则是根据布局文件名来的,比如 activity_main.xml,那么生成的类就是 ActivityMainBinding。ActivityMainBinding 类是一个抽象类,它的实现类是 ActivityMainBindingImpl(也在build目录下),它的作用就是实现了 Data Binding 的一系列功能和特征。什么功能和特征?别急,后面会讲到!我们先看如何使用 Data Binding 布局。
布局使用
前面说了 Data Binding 的功能和特征在自动生成的 ActivityMainBinding 类中,那我们需要获取到 ActivityMainBinding 对象才能使用它。使用了 Data Binding 的布局在 Activity 中就不能直接调用 setContentView(R.layout.activity_main)设置布局了,你得这样做:
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
在 Activity 中通过 DataBindingUtil 工具类的 setContentView 方法设置布局到 Activity 当中,同时返回
ActivityMainBinding 对象。有了 ActivityMainBinding 对象,我们就可以去体验 Data Binding 的魅力了。
如果你是在代码运行时创建View,想使用 Data Binding,你可以通过如下方式获取绑定类:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你是在 Fragment、ListView 或者 RecyclerView 的适配器中使用 Data Binding,你可以使用
DataBindingUtil 类的 flatflate() 方法,如下面的代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//或者
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
使用绑定类中的 View
使用了 Data Binding 我们不在需要 findViewById() 获取对象了,因为 Data Binding 编译插件会检索布局文件中的控件,把已经声明了 ID 的 View 自动创建对象到 ActivityMainBinding 类中,直接获取使用即可。对象的命名规则是根据控件 ID 名来的,比如 android:id="@+id/tv_info"
,自动生成后的对象名为 tvInfo
(去除下划线,并以驼峰格式命名):
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.tvInfo.setText("我是使用Data Binding的Demo");
}
以上是通过 ActivityMainBinding 对象获取布局中 TextView 的对象 tvInfo
并设置新值。
数据绑定
要进行数据绑定,首先要在布局中声明绑定变量,声明变量需要使用 data 标签以及 variable 标签。data 标签里面是用来做一些声明,比如声明变量,导入数据类型等。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.example.databindingdemo.bean.UserInfo" />
<variable
name="user"
type="UserInfo" />
</data>
.....
</layout>
这是一个用户信息界面的数据绑定,import 标签是导入一个数据类型,variable 标签是声明一个类型为 UserInfo 的变量 user
。
绑定属性
数据实体类:
public class UserInfo {
public String name;
public int age;
public int sex;
public String sign;
}
完整布局:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.example.databindingdemo.bean.UserInfo" />
<variable
name="user"
type="UserInfo" />
</data>
<android.support.constraint.ConstraintLayout 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">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@{@string/name(user.name), default=姓名}"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@{@string/age(String.valueOf(user.age)), default=年龄}"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_name" />
<TextView
android:id="@+id/tv_sex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@{@string/sex(user.sex == 1?@string/sex_man:@string/sex_woman), default=性别}"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_age" />
<TextView
android:id="@+id/tv_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@{user.sign, default=个性签名}"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_sex" />
</android.support.constraint.ConstraintLayout>
</layout>
在 layout 中的表达式使用“@{}”
语法在属性中编写,完整布局中有多个这样的表达式,比如为 TextView 设置值:
<TextView
android:id="@+id/tv_sign"
...
android:text="@{user.sign, default=个性签名}"
...
/>
这里的“@{}”
中的表达式是从 user
对象(前面声明的变量)中获取 sign
字段绑定到了 TextView 中,后面的 default
属性是用来设置布局预览时的值,它是可选字段,也可以直接写成 android:text="@{user.sign}"
,只不过这样写在布局预览时就不会显示内容。
小插曲:本 Demo 一开始是基于 Ubuntu 系统写的,在 xml 中写中文是没问题的,但后来在 Win 系统上运行 Demo 则报
Caused by: org.apache.xerces.impl.io.MalformedByteSequenceException: Invalid byte 2 of 3-byte UTF-8 sequence.
错误,这是因为 Win 在编译中文时的编码问题(Demo 已修正)。在此提醒广大读者,不要偷懒,应该把所有中文都写到 string.xml 中去,在 xml 中这样引用android:text="@{@string/name(user.name), default=@string/default_name}"
,特别是在使用了 Data Binding 的情况下,这样会减少很多迷之编译错误。
绑定方法
前面讲了绑定对象的属性,还可以绑定对象的方法,比如:
public class UserInfo {
private String name;
...
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
}
有很多时候我们的对象属性是私有的,我们提供了 Getter 和 Setter 方法,在绑定的时候我们可以这样写android:text="@{user.name}"
, 为什么不是android:text="@{user.getName()}"
呢?这是因为 Data Binding 内部做了处理,它会把 getName()
方法解析为 name()
,所以我们可以直接使用表达式 @{user.name}
。
注意:只要项目中开启了Data Binding功能,所有的 getxxx 方法都遵循这个规则。
对于类中的字段、 getter 方法和 ObservableField 对象都可以在表达式中使用格式引用:
android:text="@{user.name}"
在编译生成的绑定类中也会自动检查空值并避免空指针异常。 例如,在表达式 @{ user.name }
中,如果 user
为 null
,则 user.name
的默认值为 null
。 如果表达式 @{ user.age }
,其中 age
类型为 int
,那么数据绑定使用默认值 0
,其他数据类型类似。
设置数据
布局写好后,我们只需在代码中绑定对应数据即可:
private ActivityUserBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
UserInfo info = new UserInfo();
info.name = "木易";
info.age = 28;
info.sex = 1;
info.sign = "问君能有几多愁,恰似一杯二锅头";
binding.setUser(info);
}
以上是获取 ActivityUserBinding
对象,调用 setUser
方法绑定数据,setUser
方法是自动编译生成的,命名规则是根据布局中声明的变量 user
字段(<variable name="user" type="UserInfo" />
)而定的,在布局中声明的所有变量都会自动生成 Getter 和 Setter 方法。当调用 setUser 方法时,会自动触发所有绑定了 UserInfo 对象的 View 重新赋值,当界面刷新时 UserInfo 中的信息就显示在界面上了。
至此 Data Binding 算是用上了,但仅仅只是打了个照面,好比能运行 “hello word” 了。这只是一个开始,接下来的几篇文章会比较详细的讲解 Data Binding 的功能和特性。
此篇到这里就结束了,可以查看下一篇 Data Binding 详解(二)-布局和绑定表达式。
如果你觉得文章有帮助到你,记得点个喜欢以表支持,同时欢迎你的指正和建议。十分感谢!