Google官方开发库——DataBinding的使用(一)

概述

介绍DataBinding的使用方法,并提供样例项目。

介绍DataBinding

DataBinding是Google 2015年7月推出的一个库。

DataBinding能大大减少业务逻辑与布局之间的胶水代码。

DataBinding是一个支持库,它可以向后兼容到Android 2.1(API level 7+)。

为了能正常使用DataBinding,Android插件Gradle必须在1.5.0-alpha1及其以上。

配置DataBinding

为了使用DataBinding,首先需要在Android SDK Manager中下载Android Support Repository。

DataBinding的配置非常简单,只需要在Module级别的build.gradle中添加下面几行代码即可:

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

如果你的项目依赖使用了DataBinding的库,那么你的项目同样需要配置DataBinding。

另外,只有Android Studio 1.3及其以后的版本才支持DataBinding。

Get Started

DataBinding布局

DataBinding的布局与我们平常使用的布局稍微有一点不同。DataBinding布局的根节点是一个layout元素,然后包含一个data元素和一个View类型的视图元素。

视图元素就和我们平常使用的视图一样。布局代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="pojoUser"
type="com.bovink.databindingsample.model.PojoUser" />
<variable
name="javaBeansUser"
type="com.bovink.databindingsample.model.JavaBeansUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{pojoUser.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="@{pojoUser.lastName}" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{javaBeansUser.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="@{javaBeansUser.lastName}" />
</LinearLayout>
</LinearLayout>
</layout>

在data元素中声明的pojoUser变量和javaBeansUser变量可以在布局中使用。

1
2
3
4
5
6
7
<variable
name="pojoUser"
type="com.bovink.databindingsample.model.PojoUser" />
<variable
name="javaBeansUser"
type="com.bovink.databindingsample.model.JavaBeansUser" />

绑定表达式需要在属性中添加@{}来开启使用。代码如下:

1
2
3
4
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{pojoUser.firstName}" />

数据对象

下面是一个POJO类型的对象类:

1
2
3
4
5
6
7
8
9
10
public class PojoUser {
public final String firstName;
public final String lastName;
public PojoUser(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

像上面这种对象,很明显,一旦给对象保存数据后就再也不会被改变。

接下来是JavaBeans类型的对象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JavaBeansUser {
private final String firstName;
private final String lastName;
public JavaBeansUser(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}

对DataBinding来说,这两种对象类没什么区别。绑定表达式@{pojoUser.firstName}会直接访问第一个类对象的firstName域,对第二个类对象就会通过getFirstName()方法来访问私有域。

绑定视图与数据

DataBinding会根据布局文件的名字根据一定规则来确定绑定类的名字。这个规则即首字母大写,去掉下划线,最后在末尾加上Binding。
例如布局文件的名字是activity_data_binding_layout,那么生成的绑定类的名字就是ActivityDataBindingLayoutBinding。

绑定视图与数据的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDataBindingLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding_layout);
pojoUser = new PojoUser("Bruce", "Wayne");
binding.setPojoUser(pojoUser);
javaBeansUser = new JavaBeansUser("Clark", "Kent");
binding.setJavaBeansUser(javaBeansUser);
}

接下来运行项目即可。

下面是绑定视图与数据的另一种方式:

1
2
ActivityDataBindingLayoutBinding binding = ActivityDataBindingLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

事件绑定

DataBinding允许写一个绑定表达式来处理View分发的各种事件。事件属性的名字由监听方法的名字决定。例如,View.OnLongClickListener有一个onLongClick()方法,那么这个事件属性的名字就是android:onLongClick。DataBinding存在两种方式来处理事件。

  • 方法引用

    在绑定表达式中,你可以引用一个和监听方法签名一致的方法。当一个数据表达式是方法引用时,DataBinding就会创建一个事件监听,这个事件监听中会包含一个存放引用方法的对象,最后再将这个事件监听赋予视图。不过如果绑定表达式的值是null,那么DataBinding将不会创建一个事件监听,视图也只会被赋予一个null事件监听。

    DataBinding生成的绑定类中是按以下的流程来处理方法引用的,有兴趣的人可以去build里面看一下类源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
View view;
View.OnClickListener listener = null;
OnClickListenerImpl listenerImpl;
EventHandler eventHandler;// 这个对象就是在布局中方法引用所属的对象
if(eventHandler != null) {
listener = (((listenerImpl == null) ? (listenerImpl = new OnClickListenerImpl()) : listenerImpl).setValue(eventHandler));
}
view.setOnClickListener(listener);
public class OnClickListenerImpl implements View.OnClickListenr {
private EventHandler value;
public OnClickListenerImpl setValue(EventHandler value) {
this.value = value;
return this;
}
@Override
public void onClick(View view) {
this.value.handle(view);
}
}
public class EventHandler {
public void handle(View view) {}
}
  • 监听绑定

    即匿名表达式,当事件发生时才会执行这个匿名表达式。采用监听绑定时,DataBinding总是会创建一个事件监听给视图。当事件发生时,这个事件监听才会执行方法内部的匿名表达式。

    类似下面的流程,有兴趣的人同样可以去看一下源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
View view;
OnClickListenerImpl listenerImpl;
EventHandler eventHandler;
view.setOnClickListener(listenerImpl);
public class OnClickListenerImpl implements View.OnClickListener {
@Override
public void onClick(View view) {
if(eventHandler != null) {
eventHandler.handle()
}
}
}
public class EventHandler {
public void handle() {}
}

方法引用

方法引用有点类似给视图的android:onClick属性分配一个Activity的方法。也就是下面这种方式:

1
2
3
4
5
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doSomething"
android:text="Hello World!" />
1
2
3
4
5
public class MainActivity extends AppCompatActivity {
public void doSomething(View view) {
System.out.println("MainActivity.doSomething");
}
}

不过方法引用相比这种方式的优势在于绑定表达式是在编译时执行,所以如果引用的方法不存在或是它的签名与事件监听的方法不一致时,就会给你一个编译错误。

接下来介绍如何使用方法引用,java代码:

1
2
3
4
5
6
public class EventHandler {
public void bruceSays(View view) {
Toast.makeText(DataBindingLayoutActivity.this, "I'm BatMan.", Toast.LENGTH_SHORT).show();
}
}

xml代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="pojoUser"
type="com.bovink.databindingsample.model.PojoUser" />
<variable
name="handler"
type="com.bovink.databindingsample.databinding.databindinglayout.DataBindingLayoutActivity.EventHandler" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{handler::bruceSays}"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{pojoUser.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="@{pojoUser.lastName}" />
</LinearLayout>
</LinearLayout>
</layout>

注意,引用的方法必须和监听方法的签名一致。

监听绑定

监听绑定与方法引用类似,不过监听绑定没有参数的限制。这个功能需要Android插件Gradle的版本2.0及其以上才能使用。

在方法引用中,参数必须与监听方法的参数一致。不过监听绑定就没有这个限制,只需要返回值与监听方法的返回值一致即可。

接下来介绍如何使用监听绑定,java代码:

1
2
3
4
5
6
public class EventHandler {
public void clarkSays() {
Toast.makeText(DataBindingLayoutActivity.this, "I'm SuperMan.", Toast.LENGTH_SHORT).show();
}
}

xml代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="javaBeansUser"
type="com.bovink.databindingsample.model.JavaBeansUser" />
<variable
name="handler"
type="com.bovink.databindingsample.databinding.databindinglayout.DataBindingLayoutActivity.EventHandler" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:onClick="@{() -> handler.clarkSays()}"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{javaBeansUser.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="@{javaBeansUser.lastName}" />
</LinearLayout>
</LinearLayout>
</layout>

监听绑定的绑定表达式只能用匿名表达式作为根节点。

注意,在上面的例子中,我们没有在匿名表达式中添加view参数。监听绑定给我们提供了两种选择:要么一个参数都不加,要么就全都加上。如果你想添加参数,你也可以在绑定表达式中写上:

1
android:onClick="@{(view) -> action.kickAss(clark, bane)}"

如果你想在表达式中使用这个参数,可以像下面这样写:

1
2
3
4
5
public void runAway(View view, PojoUser whoIsRun, PojoUser chaserA, PojoUser chaserB) {
view.setBackgroundColor(Color.parseColor("#66CCFF"));
Toast.makeText(EventHandlingActivity.this, whoIsRun.lastName + " is run away from " + chaserA.lastName + " and " + chaserB.lastName, Toast.LENGTH_SHORT).show();
}
1
android:onClick="@{(view) -> action.runAway(view, bane, bruce, clark)}"

你也可以在匿名表达式中添加多个参数:

1
2
3
4
5
6
7
8
9
10
11
12
public void revenge(PojoUser enemyA, PojoUser enemyB, boolean decidedToRevenge) {
revenge = decidedToRevenge;
if (decidedToRevenge) {
Toast.makeText(EventHandlingActivity.this, "Bane wanna revenge " + enemyA.lastName + " and " + enemyB.lastName, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(EventHandlingActivity.this, "Bane was scared and ran away", Toast.LENGTH_SHORT).show();
}
}
1
2
3
4
5
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> action.revenge(bruce, clark, isChecked)}"
android:text="Does Bane decide to revenge?" />

如果你要处理的监听方法的返回值不是void,那么就像前面提到的一样,绑定表达式的返回值也必须一致。拿View.OnLongClickListener举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Action {
boolean revenge = false;
public boolean choose() {
if (revenge) {
Toast.makeText(EventHandlingActivity.this, "Bane broken Bruce's back in front of Clark", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(EventHandlingActivity.this, "Bane ran away with tears", Toast.LENGTH_SHORT).show();
}
return false;
}
}
1
android:onLongClick="@{() -> action.choose()}"

在绑定表达式无法求值时,DataBinding就会返回Java类型相应的默认值。如null对引用对象,0对int,false对boolean等等。

如果你需要用条件运算符,我们可以用void或者其他值来作为一种选择。

1
android:onLongClick="@{(view) -> view.isVisible() ? action.choose() : false}"

避免复杂监听

监听绑定的表达式非常强大,它能让你的代码非常易懂。不过另一方面,太复杂的表达式也会让你的布局代码难以理解和不容易维护。这些表达式c传递的参数应该尽量简洁。业务逻辑也应该都在回调方法的内部实现。

一些特别的点击事件需要用除了android:onClick以外的标签来避免冲突。下面几个标签就是被创建来避免冲突的:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

方法引用与监听绑定的区别

方法引用与监听绑定的主要不同点:

  • 参数的限制不同
  • 实际处理事件的方法所在的对象为空时,方法引用会给视图设置一个null事件监听,而监听绑定则仍然会给视图添加一个事件监听,只不过这个事件监听在事件触发时什么都不会发生。

样例项目

https://github.com/bovink/DataBindingSample

参考