2015年的Google IO大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),官方原生支持 MVVM 模型。数据绑定的概念并不陌生,Web开发中已经很是普遍,因此DataBinding或多或少地都借鉴了Web端开成熟的经验,其语法与使用方式都和JSP中的EL表达式非常类似。经过不断地强化,到了2016年,DataBinding已经可以支持数据双向绑定。
1. DataBinding简介
DataBinding解决了Android UI 编程的一个痛点,其主要优势在于:
- 对于MVVM的支持:官方原生支持 MVVM 模型可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性。
- 提高开发效率:
- 去掉Acitivity和Fragments中更新UI数据的代码,让业务逻辑和UI代码分离;
- XML成为UI数据的唯一真实来源;
- 减少定义view id 和使用findViewById();
- 性能高、功能强:
- 充分考虑了性能因素,高效的绑定和更新数据;
- 更安全,在编译时会发现由于错误的ID而引起的Errors;
- 保证代码在主线程经常;
在DataBinding推出之前,市面上已经有类似的第三方库在被使用,比如:
- ButterKnife;
- Android Annotations;
- RoboBinding;
DataBinding让这些依赖注入框架会慢慢地失去市场,因为在 Java代码中直接使用View变量的情况会越来越少。
但是DataBinding毕竟还在发展阶段,有些地方还值得完善:
- IDE的支持不足善:虽然DataBinding是Google亲生的,但是Android Studio中的支持还不是不尽如人意,尤其是在XML文件中代码提示有待加强;
- 报错信息不直接:这个是老生常谈的问题,虽然DataBinding在编译时能发现数据绑定的错误,但是报错信息往往不直观,需要用户在很多错误信息中自己去找错误点。
- 没有重构的支持:这可以说是所有数据绑定框架都会面临的问题,因为数据的绑定都是在XML中,一旦用户需要继承原来的代码,就压面临这将所有数据绑定代码重新编写。
2. DataBinding的基本使用
准备工作
- 确保Android stuido的版本一定要大于1.3。
- Android的Gradle插件版本不低于 1.5.0-alpha1:
classpath 'com.android.tools.build:gradle:1.5.0'
- 然后修改对应模块(Module)的 build.grade:
android {
....
dataBinding {
enabled = true
}
}
工程创建完成后,我们通过一个最简单的例子来说明 Data Binding 的基本用法。
布局文件
使用DataBinding之后,xml的布局文件就不再用于单纯地展示UI元素,还需要定义UI元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了layout,并且新增了一个节点data,用来定义绑定的数据。
....
data节点的作用就像一个桥梁,Activity中将data需要数据传入,XML定义data数据绑定的位置,搭建了 View 和 Model 之间的通路,也就是实现MVVM的ViewModel。
下面说明下如何在XML定义data节点:我们先在 xml 布局文件的data节点中声明variable节点,这个节点将会定义变量的名称、类型,为UI元素提供数据(例如将String绑定到TextView的android:text属性上)。
其中Presenter类时用来绑定方法的类,我们稍后再说;Student类是我们定义的一个类,用来呈现数据,其定义如下:
public class Student extends BaseObservable{
private String firstName;
private String lastName;
public void setFirstName(String firstName) {
this.firstName = firstName;
//提示该属性刷新了
notifyPropertyChanged(BR.firstName);
//提示所有的属性都刷新
//notifyAll();
}
//注解来提示Binding生成这个字段
@Bindable
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
@Bindable
public String getFirstName() {
return firstName;
}
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return "Student{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '}';
}
}
Studnet类继承了BaseObservable,这个是数据更新时所需要的,我们稍后在再来讨论。
当我们设置DataBinding生效后,如果一个xml文件的被如上这般设置了Data节点,编译器就会自动生成一个继承自 ViewDataBinding 的类(build 目录下)。其命名规则也是固定的:如果 xml 的文件名叫 activity_main.xml,那么生成的类就是 ActivityMainBinding。
绑定数据变量
下面需要修改MainActivity的onCreate方法,用 DatabindingUtil.setContentView()来替换掉setContentView(),然后创建一个Student对象,通过mBinding.setStudent(mStudent)与 variable 进行绑定。
Student mStudent = new Student("guo","chengqian");
private ActivityMainBinding mBinding;
Boolean visiblity = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
//绑定变量
mBinding.setStudent(mStudent);
mBinding.setPresenter(new Presenter());
mBinding.setVisibility(visiblity);
}
ActivityMainBinding类中所有的set方法是根据variable名称生成的。我们在XML文件中定义了三个变量,所以会生成三个set方法。
在XML中使用变量
当数据传入ViewDataBinding与Variable绑定之后,xml的UI元素就可以直接使用了。
直接在对应属性上@{variable}就可以使用该变量的值以及对应的属性。
现在运行程序,就会看到我们传入Student的姓名已经显示出来了
3. 事件方法绑定
仅仅是在UI上绑定数据还远远不够,UI上的响应事件也需要被处理,因此需要要把响应事件的方法绑定到View上。
事件方法的绑定有两种方法:第一种是方法引用,这种方法要求函数的参数个数,类型以及顺序都会原生的事件方法相同;第二种方法是使用lambda表达式,这种方法更为灵活,可以自定义参数。
我们来看下例子:
这是一个方法类的实现,之前在xml的data节点中已经声明了变量,在Activity中也进行了绑定。
//方法类
public class Presenter{
//方法引用 参数要求符合接口定义
public void onTextChanged(CharSequence s, int start, int before, int count){
mStudent.setFirstName(s.toString());
//如果已经为该类继承了BaseObservable 就不需要再赋值了;
//mBinding.setStudent(mStudent);
}
//方法引用,参数要求符合接口定义;
public void onClick(View view){
Toast.makeText(MainActivity.this,"onClick!",Toast.LENGTH_SHORT).show();
}
//自定义方法
public void onClickListenerBinding(Student student){
Toast.makeText(MainActivity.this, student.getLastName(),Toast.LENGTH_SHORT).show();
}
}
首先在EidtText控件中使用,将onTextChanged方法绑定。
这样一旦输入的内容改变,变量student中对应的firstName属性也会跟着改变。
同样,我们将onClick方法绑定到一个TextView上
但是这种方法不够灵活,绑定的方法必须和View所要求的接口参数一致。为了更为灵活的绑定方法,还可以使用Lambda表达式。
lambda表达式实际上也是创建了匿名函数,然后再这个匿名函数中调用了Presenter中的方法,只不过省去了匿名函数的申明,只保留最为核心的逻辑部分。
此外,lambd表达式支持传入Context参数,比如Presenter中定义这样的函数
public void onStudentLongClick(Student student, Context context){
Toast.makeText(LambdaActivity.this, "LongClick: "+student.toString(), Toast.LENGTH_SHORT).show();
}
Button中可以这样调用,Context参数会被自动生成:
4. DataBinding所支持的表达式
为了扩展更为强大的功能,DataBinding在使用变量时支持一下表达式:
- 运算符:Mathematical + - / * %
- 字符串连接:String concatenation +
- 逻辑运算:Logical && ||
- 位操作: Binary & | ^
- Unary + - ! ~
- 移位操作:Shift >> >>> <<
- 比较操作:Comparison == > < >= <=
- 类型判断:instanceof
- Grouping ()
- Literals - character, String, numeric, null
- 类型转化:Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
但不支持的表达式:
- this
- super
- new
- Explicit generic invocation
Null Coalescing 运算符
DataBinding对于引用变量是否为空,也有特殊的支持,使用符号??。
android:text="@{student.firstName ??student.lastName}"
就等价于
android:text="@{student.firstName != null ? student.firstName : student.lastName}"
5. 在include文件里使用BataBinding
为了让Xml布局文件更为简洁易懂,对于重复使用的布局可以单独建立布局文件,然后使用include导入。DataBinding也考虑到了这种使用情况。可以使用bind:variable_name导入数据变量。
需要注意的是:
- 一定要使用app命名空间
xmlns:app="http://schemas.android.com/apk/res-auto"
- 必须在ViewGroup的根布局中导入布局文件,如果在非根节点的ViewGroup中使用include会导致错误;
6. 在SubView中的使用
在SubView的使用方式和在Include中使用是类似的。
然后在Activity中通过DataBindingUtil获得SubView对象,然后填充View;
//viewstub inflate;
View inflate = mBinding.viewStub.getViewStub().inflate();
7. 双向绑定
上面提的内容都是单向绑定,也就是把后台的数据绑定到前台显示出来。DataBinding如果只有如此功能,也不足为奇,最重要的功能点还是还在于双向绑定,前台显示的数据改变也会自动影响后台的数据。
首先我们新建一个数据对象类
public class FormModel extends BaseObservable {
private String name;
private String password;
@Bindable //该注解表示数据绑定
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//通知name属性发生改变
notifyPropertyChanged(BR.name);
}
//注解标志该属性是绑定的
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知有属性发生改变
notifyPropertyChanged(BR.password);
}
public FormModel(String name, String password) {
this.name = name;
this.password = password;
}
}
该类继承了BaseObservable,在getter方法之前使用@Bindable,标记该属性就是需要绑定的,会在BR类中生成对应的ID;在setter方法中notifyPropertyChanged(BR.ID)
,通知对应ID的属性发生了改变。
然后在XML中使用“@={variable}”表示数据绑定,如下。
最后在Activity中传入变量。
public class TowWayBindingActivity extends AppCompatActivity {
ActivityTowWayBindingBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.
setContentView(this,R.layout.activity_tow_way_binding);
FormModel model = new FormModel("user", "12345");
mBinding.setModel(model);
//属性改变监听器
model.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if(BR.name == propertyId)
Toast.makeText(TowWayBindingActivity.this, "姓名改变",
Toast.LENGTH_SHORT).show();
}
});
}
}
这是运行程序就会发现,在前端输入姓名时,其他绑定name属性的地方也会改变,说明后台数据也响应改变了。
8. 表达式链与隐式更新
当多个View的属性相互关联式时,可以通过表达式链实现隐式更新,比如:
9. 添加动画
数据绑定必然涉及到UI的变化,默认情况下往往都是很生硬的UI变化,没有任何过度。DataBinding也提供办法来插入动画。
实现方法也很简单,就是设置一个重新绑定时的回调函数addOnRebindCallback,在函数中设置当前View的UI变化时开启一定延时。
public class AnimationActivity extends AppCompatActivity {
ActivityAnimationBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this,R.layout.activity_animation);
mBinding.setPresenter(new AnimatorPresenter());
//设置重绑定回调函数
mBinding.addOnRebindCallback(new OnRebindCallback() {
@Override
public boolean onPreBind(ViewDataBinding binding) {
ViewGroup viewGroup = (ViewGroup) binding.getRoot();
//开启延时位移动画
TransitionManager.beginDelayedTransition(viewGroup);
return true;
}
});
}
public class AnimatorPresenter{
public void onCheckedChanged(boolean isChecked){
mBinding.setShowImage(isChecked);
}
}
}
在XML中正常使用DataBinding
这样图片在显示或者出现的过程中都会添加一个CheckBox位移匀速渐变的动画效果。
需要说明的是,虽然布局文件中CheckBox原本并没有onCheckedChanged属性,但是CheckBox类有对应的属性,并且设有setOnCheckedChanged方法来设置该属性,所以也是可以进行绑定的。
按照惯例,给出Demo源码:
https://github.com/sunrongxin7666
欢迎大家指正批评
需要说明的是,由于篇幅的限制,还有动态绑定和在RecyclerView中使用没有介绍,如果感兴趣请读者参考一下两篇文章,基于以上的内容,相信读者不会很难理解。
Android DataBinding完全解析
精通 Android Data Binding