Data Binding Library
导航:
本文档解释了如何使用数据绑定库来编写声明式布局,并尽量减少绑定应用程序逻辑和布局所需的胶合代码。
数据绑定库提供了灵活性和广泛的兼容性 - 这是一个支持库,所以你可以在Android 2.1(API级别7+)的所有Android平台上使用它。
要使用数据绑定,Android Plugin for Gradle 1.5.0-alpha1 或更高版本是必需的。了解如何更新Android Plugin for Gradle。
1.配置环境
要开始使用数据绑定,请从Android SDK管理器的支持库中下载库。
要配置应用程序以使用数据绑定,请将dataBinding
元素添加到应用程序模块中的build.gradle
文件中。
使用下面的代码片段来配置数据绑定:
android {
....
dataBinding {
enabled = true
}
}
如果您的应用程序模块依赖于使用数据绑定的库,则您的应用程序模块也必须在其build.gradle
配置数据绑定。
另外,请确保您使用的是Android Studio的兼容版本。Android Studio 1.3及更高版本支持数据绑定,如Android Studio支持数据绑定中所述。
2.数据绑定编译器V2
Android Gradle插件3.1.0 Canary 6附带一个可选的新编译器。要开始使用它,请更新您的gradle.properties文件以包含以下行:
android.databinding.enableV2=true
在编译器v2中:
-
ViewBinding
类是在java编译器之前由Android Gradle Plugin生成的。这可以避免由于不相关的原因导致java编译失败而导致太多的错误肯定错误。 - 在V1中,编译应用程序时会重新生成库的绑定类(以共享生成的代码并访问最终的“BR”和“R”文件)。在V2中,库保持其生成的绑定类以及映射器信息,这为多模块项目显着提高了数据绑定性能。
请注意,这个新的编译器是向后不兼容的,所以用v1编译的库不能被v2使用,反之亦然。
V2还会删除一些很少使用的功能来允许这些更改:
- 在V1中,一个应用程序能够提供绑定适配器,可以覆盖依赖项中的适配器。在V2中,它只对自己的模块/应用程序及其依赖项中的代码生效。
- 以前,如果一个布局文件在两个或多个不同的资源配置中包含一个
View
具有相同id
但不同类的数据,则数据绑定将查找最常见的父类。View当配置之间的类型不匹配时,它将始终默认为。 - 在V2中,不同的模块不能在清单中使用相同的包名称,因为数据绑定将使用该包名来生成绑定映射器类。
3.数据绑定布局文件
3.1编写你的第一套数据绑定表达式
数据绑定布局文件稍有不同,layout
作为根标签,后跟data
元素和 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的文本被设置为用户的firstName属性:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data Object
3.2 数据对象
现在让我们假设你有一个普通的Java对象(POJO)用户:
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 android:text
属性的表达式将访问前一类中的firstName
字段和后一类中的getFirstName()
方法。或者,如果firstName()
方法存在,也将被解析。
3.3 绑定数据
默认情况下,将根据布局文件的名称生成一个Binding类,将其转换为Pascal格式并将Binding
后缀添加到该文件中。上面的布局文件是main_activity.xml
这样的生成类是MainActivityBinding
。这个类将布局属性(例如user
变量)的所有绑定保存到布局的视图中,并知道如何为绑定表达式赋值。创建绑定的最简单方法是在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);
}
你完成了!运行应用程序,你会看到用户界面中的测试user
。或者,您可以通过以下方式获取视图:
MainActivityBinding binding =
MainActivityBinding.inflate(getLayoutInflater());
如果您在ListView或RecyclerView适配器内使用数据绑定项目,则可能更愿意使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Event Handling
3.4 事件处理
数据绑定允许您编写表达式来处理从视图中分派的事件(例如onClick)。除少数例外,事件属性名称由侦听器方法的名称来管理。View.OnLongClickListener 有一个方法 onLongClick(),所以这个事件的属性是android:onLongClick
。处理事件有两种方法。
- 方法引用:在你的表达式中,你可以引用符合侦听器方法签名的方法。当表达式评估为方法引用时,数据绑定将方法引用和所有者对象包装在侦听器中,并将该侦听器设置在目标视图上。如果表达式求值为null,则数据绑定不会创建侦听器,而是设置空侦听器。
- 监听器绑定:这些是在事件发生时被评估的lambda表达式。数据绑定总是创建一个监听器,它在视图上设置。事件发送时,监听器评估lambda表达式。
3.4.1方法引用
事件可以直接绑定到处理方法,类似于 android:onClick
可以分配给Activity中的方法。与View#onClick
属性相比,一个主要的优点是表达式在编译时被处理,所以如果方法不存在或者它的签名不正确,你会收到一个编译时错误。
方法引用和监听器绑定的主要区别在于实际的监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果您喜欢在事件发生时评估表达式,则应该使用监听器绑定。
要将事件分配给其处理程序,请使用常规绑定表达式,其值是要调用的方法名称。例如,如果你的数据对象有两个方法:
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.MyHandlers"/>
<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>
请注意,表达式中方法的签名必须与Listener对象中方法的签名完全匹配
3.4.2监听器绑定
监听器绑定是事件发生时运行的绑定表达式。它们类似于方法引用,但是它们允许您运行任意的数据绑定表达式。此功能适用于Gradle 2.0版及更高版本的Android Gradle插件。
在方法引用中,方法的参数必须与事件侦听器的参数匹配。在监听器绑定中,只有你的返回值必须与监听器的期望返回值相匹配(除非它预期为void)。例如,您可以有一个具有以下方法的演示者类:
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>
监听器仅允许只允许由lambda表达式作为根元素的表达式。在表达式中使用回调函数时,数据绑定会自动为事件创建必要的侦听器和注册表。当视图触发事件时,数据绑定将评估给定的表达式。就像在常规的绑定表达式中一样,当这些监听器表达式被评估的时候,你仍然可以获得数据绑定的null和线程安全性。
请注意,在上面的例子中,我们没有定义view
传入参数onClick(android.view.View)。监听器绑定为监听器参数提供了两个选择:您可以忽略该方法的所有参数或将其全部命名。如果您想要命名参数,则可以在表达式中使用它们。例如,上面的表达式可以写成:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者如果你想使用表达式中的参数,它可以如下工作:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
---
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
您可以使用多于一个参数的lambda表达式:
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
的值,则表达式必须返回相同类型的值。例如,如果要监听长按事件,则应该返回表达式boolean
。
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
--------
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于是null
对象而无法评估表达式,Data Binding将返回该类型的默认Java值。例如,null
返回引用类型,0
返回 int
, false
返回 boolean
等。
如果您需要使用谓词(例如三元)表达式,则可以将 void
用作符号。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免复杂的监听
监听器表达式非常强大,可以让你的代码非常容易阅读。另一方面,包含复杂表达式的监听会使您的布局难以阅读和维护。这些表达式应该像从UI中传递可用数据到回调方法一样简单。您应该在您从侦听器表达式调用的回调方法内实现任何业务逻辑。
存在一些专门的单击事件处理程序,它们需要一个属性, android:onClick
以避免冲突。已经创建了以下属性以避免这种冲突:
class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
4.布局细节
4.1 import
data
元素中可以使用零个或多个import
元素,这些就像在Java中一样可以轻松地引用布局文件中的类。
<data>
<import type="android.view.View"/>
</data>
现在,可以在你的绑定表达式中使用视图:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当有类名冲突时,其中一个类可能会被重命名为“alias:”
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
现在,Vista
可能会以com.example.real.estate.View
,View
可能以android.view.View
在布局文件内引用。导入的类型可以用作变量和表达式中的类型引用:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
注意:Android Studio尚未处理导入,因此导入变量的自动填充可能无法在您的IDE中工作。您的应用程序仍然可以正常编译,您可以通过在变量定义中使用完全限定的名称来解决IDE问题。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
当在表达式中引用静态字段和方法时,也可以使用导入的类型:
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
就像在Java中一样,java.lang.*
自动导入。
4.2 Variables
data
元素内可以使用任意数量的variable
元素,每个variable
元素描述可以在布局上设置的属性,以用于布局文件中的绑定表达式。
<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>
变量类型在编译时被检查,所以如果一个变量实现Observable或者是一个observable collection,那么这个类型应该被反映出来。如果变量是不实现Observable
接口的基类或接口,将不会观察变量!
当不同的配置文件(例如横向或纵向)有不同的布局文件时,变量将被合并。这些布局文件之间不得存在冲突的变量定义。
生成的绑定类将为每个描述的变量设置一个setter和getter。变量将采用默认的Java值,直到调用者被调用 - null
是引用类型,0
是 int
,false
是boolean
等。
根据需要生成一个名为context
的特殊变量用于绑定表达式。context
是视图的getContext()得到的Context
。context
变量将被具有该名称的显式变量声明覆盖。
4.3自定义绑定类名称
默认情况下,根据布局文件的名称生成一个Binding类,以大写字母开头,删除下划线(_)并大写下面的字母,然后后缀“Binding”。这个类将被放置在模块包下的数据绑定包中。例如,布局文件contact_item.xml
将生成 ContactItemBinding
。如果模块包是 com.example.my.app
,那么它将被放入com.example.my.app.databinding
。
绑定类可以通过调整class
元素的属性来重命名或放置data
在不同的包中。
<data class="ContactItem">
...
</data>
这将在模块包中的数据绑定包中生成绑定类ContactItem
。如果该类应该在模块包中的其他包中生成,则可以用.
作为前缀:
<data class=".ContactItem">
...
</data>
在这种情况下,ContactItem
直接在模块包中生成。如果提供完整的软件包,则可以在任何软件包中:
<data class="com.example.ContactItem">
...
</data>
4.4 Includes
通过使用应用程序名称空间和属性的变量名,变量可以被传递到包含布局的包含绑定中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
在这里,name.xml
和contact.xml
布局文件中都必须有一个user
变量。
数据绑定不支持include
作为merge
元素的直接子元素。例如,不支持以下布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
4.5 1. 表达式语言
共同特征
表达式语言看起来很像Java表达式。这些是一样的:
- 数学的:
+ - / * %
- 字符串连接:
+
- 逻辑:
&& ||
- 二进制:
& | ^
- 一元:
+ - ! ~
- 转移:
>> >>> <<
- 对照:
== > < >= <=
instanceof
- 分组:
()
- 文字 - 字符,字符串,数字,
null
- Cast
- 方法调用
- 字段访问
- 数组访问
[]
- 三元操作符
?:
例子:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}
不支持的操作
您可以在Java中使用的表达式语法中缺少一些操作。
this
super
new
- 明确的泛型调用
空合并运算符
null合并运算符(??
)选择左操作数(如果不是null)或右(如果为空)。
android:text="@{user.displayName ?? user.lastName}"
这在功能上等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用
第一个已经在上面的编写第一个数据绑定表达式中讨论过了:简短形式的JavaBean引用。当一个表达式引用一个类的属性时,它对字段,获取器和ObservableFields使用相同的格式。
android:text="@{user.lastName}"
避免空指针异常
生成的数据绑定代码自动检查空值并避免空指针异常。例如,在 @{user.name}
表达式中如果user
为null, user.name
将被赋予其默认值(null)。如果你是引用user.age
,年龄是一个int
,那么它将默认为0。
集合
公共集合:数组,列表,稀疏列表和地图,为了方便可以使用[]
操作符。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
字符串文字
在属性值周围使用单引号时,在表达式中使用双引号很容易:
android:text='@{map["firstName"]}'
也可以使用双引号来包围属性值。这样做时,字符串文字应该使用'
或者反引号( ` )。
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
资源
使用正常语法可以将资源作为表达式的一部分进行访问:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和复数可以通过提供参数来评估:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当一个复数有多个参数时,所有参数都应该传递:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
有些资源需要明确的类型评估。
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
5.数据对象
任何普通的旧Java对象(POJO)都可以用于数据绑定,但修改POJO不会导致UI更新。数据绑定的真正威力可以通过给你的数据对象提供在数据改变时通知的能力。有三种不同的数据更改通知机制: 可观察对象, 可观察字段和 可观察集合。
当这些可观察的数据对象之一被绑定到UI并且数据对象的属性改变时,UI将被自动更新。
5.1可观察的对象
实现Observable接口的类将允许绑定将单个侦听器附加到绑定对象,以侦听该对象上所有属性的更改。
Observable接口具有添加和删除侦听器的机制,但通知由开发人员决定。为了简化开发,创建了一个BaseObservable基类来实现监听器注册机制。数据类实现者仍然负责通知属性何时更改,通过分配一个Bindable注释给getter并且在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类文件将在模块包中生成。如果数据类的基类不能改变,那么Observable将会被实现,通过使用PropertyChangeRegistry去存储和有效地通知侦听器。
5.2 可观察字段
Observable类大有内涵,所以开发者想要节省时间和添加有几个属性,可以使用ObservableField和它的兄弟姐妹
ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,和ObservableParcelable。
ObservableFields
是具有单个字段的自包含可观察对象。原始版本在访问操作期间避免装箱和取消装箱。要使用,请在数据类中创建一个公共final字段:
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和get访问器方法:
user.firstName.set("Google");
int age = user.age.get();
5.3 可观察的集合
一些应用程序使用更多的动态结构来保存数据,可观察集合允许对这些数据对象进行键控访问。当键是引用类型(如String)时ObservableArrayMap非常有用。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以通过String键访问map
:
<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"/>
当键是一个整数时使用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"/>
6.生成的绑定
生成的绑定类将布局变量与布局中的视图链接起来。如前所述,绑定的名称和包可能是 自定义的。生成的绑定类全部扩展ViewDataBinding。
6.1创建
应该在inflation
之后立即创建绑定,以确保在绑定到布局中带有表达式的视图之前,View层次结构不受干扰。有几种方法可以绑定到布局。最常见的是在Binding类中使用静态方法。inflate
方法inflate
了View层次结构,并将其一步绑定。有一个简单的版本,只需要一个LayoutInflater和ViewGroup:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局使用不同的inflate
机制,它可能会被分开绑定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时绑定不能预先知道。在这种情况下,绑定可以使用DataBindingUtil类创建:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
6.2带有ID的视图
每个带有ID的视图将在布局中生成一个对应的公开fianl
字段。该绑定在View层次结构上执行单个传递,提取带有ID的视图。这个机制可以比调用多个视图的findViewById更快。例如:
<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}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>
将会生成一个绑定类:
public final TextView firstName;
public final TextView lastName;
ID在数据绑定时不是必须的,但是仍然有一些情况下代码仍然需要访问视图。
6.3变量
每个变量将被赋予访问器方法。
<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>
将在绑定中生成setter和getters:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
6.4ViewStubs
ViewStub与正常view
有些不同。他们从不可见的时候开始,当他们要么变得可见时,或者被明确地告知inflate
时,他们通过inflate
另一种布局来取代布局。
由于ViewStub
本质上从View层次消失,绑定对象中的View也必须消失以允许收集。因为view
是最终的,所以一个ViewStubProxy对象代替了这个ViewStub
视图,当开发者存在的时候,它允许开发人员访问ViewStub,并且在ViewStub
被inflate
时也可以访问inflate
的View层次结构。
当inflate
另一个布局时,必须为新的布局建立绑定。因此,ViewStubProxy
一定监听ViewStub
的ViewStub.OnInflateListener,同时建立绑定。由于只有一个可以存在,所以ViewStubProxy
允许开发者设置一个OnInflateListener
,在建立绑定之后它将被调用
6.5高级绑定
动态变量
有时,特定的绑定类是不知道的,例如,RecyclerView.Adapter针对任意布局的操作将不知道具体的绑定类。它仍然必须分配绑定值onBindViewHolder(VH, int)
。
在这个例子中,RecyclerView绑定的所有布局都有一个“item”变量。所述的BindingHolder
具有getBinding
方法返回基础的ViewDataBinding。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
6.6 立即绑定
当变量或可观察对象变化时,绑定将被安排在下一帧之前改变。但有时候,绑定必须立即执行。要强制执行,请使用该executePendingBindings()方法。
6.7后台线程
只要不是集合,就可以在后台线程中更改数据模型。数据绑定将在评估时本地化每个变量/字段,以避免任何并发问题。
7. 属性Setters
每当绑定值发生变化时,生成的绑定类必须使用绑定表达式在视图上调用setter方法。数据绑定框架可以自定义调用哪个方法来设置值。
7.1 自动 Setters
对于一个属性,数据绑定试图找到方法setAttribute。属性的命名空间并不重要,只有属性名称本身才是重点。
例如,与TextView属性关联的表达式 android:text
将查找setText(String)。如果表达式返回一个int,那么数据绑定将搜索一个setText(int)方法。请注意让表达式返回正确的类型,如果需要的话就进行转换。请注意,即使给定名称不存在任何属性,数据绑定也可以工作。然后,您可以使用数据绑定轻松地为任何‘创建’的属性进行setter。例如,support DrawerLayout没有任何属性,但是有很多setter。您可以使用自动设置器来使用其中的一个。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
7.2重命名Setters
一些拥有setters
方法的属性与名称不符合。对于这些方法,属性可能通过BindingMethods注释与setter
相关联。这必须与一个类相关联,并包含BindingMethod注释,每个重命名的方法一个。例如,android:tint
属性确实与setImageTintList(ColorStateList)关联,而不是 setTint
。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
开发人员不太可能需要重命名setter;android框架的属性已经实现了。
7.3自定义setters
一些属性需要自定义绑定逻辑。例如,android:paddingLeft
属性没有关联的setter
。相反,setPadding(left, top, right, bottom)
存在。带BindingAdapter注释的静态绑定适配器方法,允许开发人员定制如何调用属性的setter。
Android属性已经创建BindingAdapter
。例如,这里是一个用于paddingLeft
:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
绑定适配器对其他类型的自定义非常有用。例如,一个自定义的加载器可以被调用脱机线程来加载一个图像。
当发生冲突时,开发人员创建的绑定适配器将覆盖数据绑定默认适配器。
您也可以让适配器接收多个参数。
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
---
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
如果imageUrl和 error都用于ImageView且imageUrl是字符串,并且error是drawable
,则将调用此适配器。
- 自定义名称空间在匹配过程中被忽略。
- 您也可以为android命名空间编写适配器。
绑定适配器方法可以选择性的在其处理程序中使用旧值。采用新旧价值的方法,首先应该拥有属性的旧值,其次是新的值:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
事件处理程序只能用于接口或一个有抽象方法的抽象类。例如:
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
当一个监听器有多个方法时,它必须被分成多个监听器。比如View.OnAttachStateChangeListener有两种方法:onViewAttachedToWindow()和onViewDetachedFromWindow()。然后我们必须创建两个接口来区分它们的属性和处理程序。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因为更改一个侦听器也会影响另一个侦听器,所以我们必须有三个不同的绑定适配器,一个用于每个属性,另一个用于两个,如果它们都被设置。
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
上面的例子比正常情况稍微复杂,因为View使用add和remove来代替View.OnAttachStateChangeListener的set方法android.databinding.adapters.ListenerUtil
类可以帮助跟踪以前的监听,让他们可以在绑定Adaper中被删除的。
通过用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
注释OnViewDetachedFromWindow
和OnViewAttachedToWindow
接口,数据绑定代码生成器知道监听器只在Honeycomb MR1和新设备生成;同样的情况在addOnAttachStateChangeListener(View.OnAttachStateChangeListener)上发生。
8. 转换器
8.1对象转换
从绑定表达式返回一个对象时,将从自动,重命名和自定义setter
中选择一个setter
。该对象将被转换为所选setter的参数类型。
这对于那些使用ObservableMaps
来保存数据的人来说非常方便。例如:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
将返回一个userMap
对象,并且将对象自动转换为setText(CharSequence)
中的参数类型。当参数类型可能混淆时,开发人员需要在表达式中输入。
8.2 自定义转换
有时转换应该在特定类型之间自动进行。例如,设置背景时:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在这里,背景需要一个Drawable
,但颜色是一个integer
。不管期望 Drawable
或是返回一个integer
,int
应该被转换成一个ColorDrawable
。这个转换是通过一个带有BindingConversion注解的静态方法完成的:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
请注意,转换只发生在setter级别,所以不允许混合类型如下所示:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
9. Android Studio支持数据绑定
Android Studio支持数据绑定代码的许多代码编辑功能。例如,它支持数据绑定表达式的以下功能:
注意:数组和 泛型类型(如Observable类)可能会在没有错误时,显示错误。
预览窗格显示数据绑定表达式的默认值(如果提供)。在下面的示例摘录了布局XML文件中的元素后,“预览”窗口将在TextView
中显示PLACEHOLDER
默认的文本值。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
如果需要在项目设计阶段显示默认值,则还可以使用工具属性而不是默认表达式值,如 Design Time Layout Attributes中所述。