本文为官网文章翻译 原文地址
这篇文档将展示如何使用 Data Binding 库声明布局以及如何最低化应用逻辑和布局之间的耦合。
Data Binding Library 非常灵活并且具有很高的兼容性---对于这个兼容库,你可以使用Android2.1以上的所有版本的Android平台。
为了使用 data binding,Gradle的 android插件需要是1.5.0-alpha1或者更高。
构建环境
为了使用 Data Binding,通过sdk manager下载 support repository 中的依赖库。
为了配置app应用 data binding,在 app module 的 build.gradle 文件中,添加 dataBinding 元素。
使用下面的代码片段进行data binding 的配资:
android{
....
dataBinding{
enabled=true
}
}
如果你的app module依赖于一个使用了 data binding 的库,你的 app module 也必须在build.gradle 文件中配置 data binding。
同样,你需要确认你的android studio 应该不低于1.3
Data Binding 布局文件
第一个data binding 表达式。
data binding布局文件和普通布局文件稍微有些不同。它以layout为根标签(root tag),随后紧跟data元素和一个view的根元素。view根元素中布局的写法于普通非绑定的布局文件相同。示例:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
data中user变量描述了一个会在布局中用到的属性。
<variable name="user" type="com.example.User"/>
在布局文件中,表达式使用"@{}"语法描述属性特征。下面是一个示例,将TextView的text属性设置为设置用户名。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data 对象
假设有一个User普通类
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
这个类型中的数据不能够改变。在一个应用中,数据一旦被读取就不再改变是很常见的场景。当然,也可以使用下面的JavaBeans
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
从数据绑定的观点来看,上面两个类是等价的。表达式 @{user.firstName}被用来设置TextView的text属性。对于前面的类来说,通过直接访问 firstName 属性的方式;对于后面的类来说,通过getFirstName()的方法。当然,也可以通过firstName()来解决这个问题,如果这个方法存在的话。
绑定数据
默认的,绑定类将被创建,它的名字以布局文件名为基础,遵循Pascal原则,以"Binding"为后缀。比如,上面的布局文件名为 main_activity.xml,因此将产生的类就是MainActivityBinding。这个类持有所有从特征(比如user变量)到布局view的绑定,并且知道怎样为绑定表达式赋值。创建绑定的最简单的方式,就是在它被inflating的时候。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
完成。另外,可以通过下面的方式来得到view:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
如果你在一个ListView或者RecyclerView的adapter中使用数据绑定,推荐使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件处理
Data Binding允许写表达式来处理view事件(比如onClick)。事件属性名和监听器(listener)方法一致(也有一些例外)。举个栗子,View.OnLongClickListener 有 onLongClick()方法,所以,该事件的属性名为 android:onLongClick。属性名所对应的,就是该事件的处理方法。下面介绍两种方式来处理事件:
- 方法引用(Method References):在表达式中,可以引用方法,该方法应该遵守监听器方法签名的特征(译者:比如参数个数、参数类型、返回值类型、访问限制等)。当一个表达式被认为是对一个方法的引用,Data Binding 将把这个引用方法和所有者对象包裹进一个监听器中,并将该监听器设置到目标view上。
- 监听绑定(Listener Bindings):当事件发生时,兰姆达表达式将被执行。Data Binding会在view上创建一个监听器,当事件被分发下来,监听器将计算兰姆达表达式。
方法引用
事件可以直接和事件处理器关联绑定,就是android:onClick被指派到Activity中的onClick方法一样。和View#onClick属性相比,方法引用的优势在于,表达式将在编译的时候进行处理,因此,如果方法不存在或者签名不正确,将报编译期错误。
方法引用和监听器绑定主要的不同在于,方法引用中,真正的监听的实现是在数据被绑定的时候,而不是在事件被触发的时候。如果你更喜欢在事件发生的时候计算表达式,应该是用监听绑定(listener binding)。
为了给事件指定处理器handler,应该使用一般的绑定的表达式,它的值就是处理事件的方法名。举个栗子
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
绑定表达式给View指定了一个点击监听器:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
注意,表达式中方法的签名应该和监听器对象中的方法签名保持匹配。
监听绑定
监听表达式将在事件发生的时候绑定事件。这种绑定方式和方法引用类似,但是,这种绑定允许你运行任意的绑定表达式。这个属性需要gradle版本2.0以上。
在方法引用中,方法的参数必须匹配事件监听器方法。在监听器绑定中,仅仅只需要返回值和监听器方法的返回值匹配即可。举个栗子
public class Presenter {
public void onSaveClick(Task task){}
}
绑定点击事件如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
监听器被表现为兰姆达表达式,兰姆达表达式必须作为整个表达式的根元素。当在表达式中使用回调函数,Data Binding将自动创建必要的监听并注册在事件上。当view上的事件发生,Data Binding将计算给定的表达式。
注意,在上面的栗子中,我们并没有定义要传入到onClick(View view)中的view参数。监听器绑定对于监听器参数提供了两种选择:如果不需要监听器(各种listener)参数,你可以选择忽略方法中的所有参数不写,如果需要使用时,应该写出使用所有监听器参数。举个栗子:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
android:onClick="@{() -> presenter.onSaveClick(task)}"
第一种写了监听器参数,第二种没有写,但是它们是等价的,因为在事实上,处理方法onSavaClick中并没有需要view。
如果你想在表达式中使用监听器参数,可以这样写:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
你可以在兰姆达表达式中使用多个参数:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
如果监听函数没有返回void,你的表达式要注意返回值的一致性.例如,如果你想监听一个long click事件,你的表达式应该返回一个布尔值:
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果表达式因为null对象而不能进行计算,Data Binding 会返回该类型下默认的Java值。对于引用类型是null,对于int就是0,对于布尔值就是false。
Data Objects
任何普通对象(POJO)都可以用来进行数据绑定。但是修改一个POJO对象并不能触发UI的更新。数据绑定真正的力量在于当数据更改时UI层能得到更新。这里,将提供三种不同的数据更改通知机制:Observable objects, observable fields 和 ovservable collections.
当这些之中的一个任意一个observable 数据对象被绑定到 UI,当数据对象发生变化时,UI将得到自动更新。
Observable Objects
当一个类实现了Observable接口,这将
Observable接口有一种机制增添和删除listener,但是,nofitying取决于开发者。为了简化开发工作,一个基类,BaseObservable被创建用来实现listener的注册机制。而数据类只需要负责当属性发生的变化的时候进行通知。这个做法,通过对getter方法进行Bindable注解,在setter中进行通知来完成。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
Bindable注解会在编辑期间产生进入BR类文件的入口。BR类文件被创建在module package中。
ObservableFields
创建一个Observable对象需要做一些工作,因此,如果开发者想要节省时间或者需要更改的属性很少,就可以使用ObservableField。相似的,还有ObserableBoolean,ObserableByte,ObservableChar,ObservableShort,ObservableInt,ObserableLong,ObserableFloat,ObservableDouble,ObservableParcelable等。ObservableFields 是一种自包含的被观察者对象,拥有一个field。
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
为了访问值,使用set和个体方法:
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
有些应用使用更加动态的结构来持有数据。Observable collections允许通过键值对访问这些数据。当键为引用类型时,ObservableArrayMap将是非常有用的。比如,当key为字符串时:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局文件中,可以通过key来访问数据:
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
当key为Integer时,ObservableArrayList是非常有用的:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局文件中,应该这样通过索引访问:
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>