转载
Android Data Binding(数据绑定)用户指南
[TOC]
1)介绍
本文档解释了如何使用数据绑定库来编写声明式布局,并且用最少的代码来绑定你的app逻辑和layouts文件。
Data Binding库既具有灵活性,又具有广泛的兼容性——它是一个support持库,因此你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。
为了使用数据绑定,需要Android插件1.5.0 - alpha1或更高版本。
2)构建环境
要开始使用Data Binding,首先需要在Android SDK Manager的支持库里下载该库。
你的app要使用Data Binding,需要添加Data Binding到gradle构建文件里,如下:
android {
....
dataBinding {
enabled = true
}
}
Data Binding插件将会在你的项目内添加必需提供的以及编译配置依赖。
请确保您使用的是Android Studio的兼容版本。Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本。
3) Data Binding Layout文件
a) Data Binding表达式
Data Binding layout文件有点不同的是:起始根标签是layout,接下来一个data元素以及一个view的根元素。这个view元素就是你没有使用Data Binding的layout文件的根元素。举例说明如下:
<?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的变量属性,使其可以在这个layout中使用:
<variable name="user" type="com.example.User"/>
在layout的属性表达式写作@{},下面是一个TextView的text设置为user的firstName属性:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
b)Data对象
假设你有一个user的plain-old Java Object(POJO):
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
这个类型的对象拥有从不改变的数据。在app中它是常见的,可以读取一次并且之后从不改变。当然也可以使用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;
}
}
从Data Binding的角度来看,这两个类是等价的。用于TextView中的android:text
属性的表达式@{user.firstName}
将访问前者POJO对象中的firstName和后者JavaBeans对象中的getFirstName()
方法。
c)Binding数据
默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”后缀。上述的layout文件是main_activity.xml
,因此生成的类名是MainActivityBinding
。此类包含从layout属性到layout的Views中所有的bindings(例如user
变量),并且它还知道如何给Binding表达式分配数值。创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:
@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);
}
就是这样,运行app后,你将会看到Test User。或者你可以通过如下获取View:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在ListView或者RecyclerView adapter使用Data Binding时,你可能会使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
d)事件处理
数据绑定允许你编写表达式来处理view分派的事件。事件属性名字取决于监听器方法名字。例如View.OnLongClickListener有onLongClick()的方法,因此这个事件的属性是android:onLongClick
。处理事件有两种方法:
方法引用:在表达式中,可以引用符合侦听器方法签名的方法。当表达式计算到方法引用时,数据绑定将方法引用和所有者对象包装在侦听器中,并将该侦听器设置为目标视图。如果表达式计算为null,那么数据绑定不会创建一个监听器,而是设置一个空监听器。
侦听器绑定:这些是在事件发生时评估的lambda表达式。数据绑定总是创建一个监听器,它在视图上设置。当事件被发送时,监听器将计算lambda表达式
方法引用
事件可以直接绑定到处理程序方法,类似于android:onClick可以被分配到活动中的方法。与视图# onClick属性相比,一个主要优点是在编译时处理表达式,因此如果该方法不存在或它的签名不正确,则会收到一个编译时错误。
方法引用和侦听器绑定之间的主要区别在于,实际的侦听器实现是在数据绑定时创建的,而不是在事件触发时创建。如果您喜欢在事件发生时评估表达式,则应该使用侦听器绑定。
要将事件分配给它的处理程序,使用一个普通的绑定表达式,其中值是要调用的方法名称。例如,如果您的数据对象有两种方法:
public class MyHandlers {
public void onClickFriend(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>
侦听器绑定
侦听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但它们允许您运行任意数据绑定表达式。这个功能可以使用Android Gradle插件来升级版本2.0和更高版本。
在方法引用中,方法的参数必须与事件监听器的参数相匹配。在侦听器绑定中,只有您的返回值必须匹配侦听器的预期返回值(除非它期望void)。例如,您可以有一个具有以下方法的presenter类
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和线程安全性。
请注意,在上面的示例中,我们还没有定义传入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的值,那么您的表达式必须返回相同类型的值。例如,如果您想要侦听长时间单击事件,您的表达式应该返回布尔值。
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于null对象不能计算表达式,那么数据绑定将返回该类型的默认Java值。例如,引用类型为null,int为0,布尔值为false等等。
如果需要使用谓词的表达式(如ternary),则可以将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)深入Layout文件
a)Import
零个或多个import元素可能在data元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:
<data>
<import type="android.view.View"/>
</data>
现在,View可以使用你的Binding表达式:
<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"/>
这样,在该layout文件中Vista
对应com.example.real.estate.View
,而View
对应android.view.View
。导入的类型可以在Variable和表达式中使用作为引用来使用:
<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还没有处理imports,所以自动导入Variable在你的IDE不能使用。您的app仍会正常编译,你可以在您的Variable定义中使用完全符合规定的名称来解决该IDE问题。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
导入的类型还可以在表达式中使用static属性和方法:
<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.*
是自动导入的。
b)Variables
在data
中可以使用任意数量的variable
元素。每一个variable
元素描述了一个用于layout文件中Binding表达式的属性。
<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>
该Variable
类型在编译时检查,因此如果一个Variable实现了Observable
或observable collection
,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!
当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。
产生的Binding类对于每一个描述的Variables都会有setter和getter。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。
c)自定义Binding类名称
默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding
封装包下。例如,所述layout文件contact_item.xml
将生成ContactItemBinding
。如果模块包是com.example.my.app
,那么它将被放置在com.example.my.app.databinding
。
Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:
<data class="ContactItem">
...
</data>
在模块封装包的databinding包中会生成名为ContactItem
的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.,如下:
<data class=".ContactItem">
...
</data>
在这个情况下,ContactItem类直接在模块包种生成。或者你可以提供整个包名:
<data class="com.example.ContactItem">
...
</data>
d)Includes
通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:
<?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
两个layout文件中必需要有user
variable
Data binding不支持包括作为合并元素的直接子元素。例如,不支持以下布局:
<?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>
e)表达式
-
常用表达式跟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,选择左边的对象;或者如果它是null,选择右边的对象:android:text="@{user.displayName ?? user.lastName}"
上的写法相当于下面的写法:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
-
属性引用
第一个已经在前边提到了
a)Data Binding
表达式:JavaBean引用的简短格式。
当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。android:text="@{user.lastName}"
-
避免 NullPointerException
Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达
式@{user.name}
中,如果user
是null,user.name
会赋予它的默认值(null)。如果你引用user.age
,age是int
类型,那么它的默认值是0。 -
集合
常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用
[]
来访问。<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']}"
-
Resources
使用正常的表达式来访问resources也是可行的:
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)}"
类型 正常引用 表达式引用 String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList 一些资源需要显式类型判断:
类型 正常引用 表达式引用 String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList
5)Data 对象
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable
对象、ObservableFields
以及observable collections
。
当这些可观察Data对象绑定到UI,Data对象属性的更改后,UI也将自动更新。
a)Observable 对象
实现android.databinding.Observable
接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。
Observable
接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable
的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个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类文件中生成一个Entry。BR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器。
b)Observable 字段
一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用·ObservableFields·。·ObservableFields·是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public 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();
c)Observable 集合
一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap
用于键是引用类型,如String
。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中,通过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);
在layout文件中,通过索引可以访问list:
<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)Binding生成
Binding类的生成链接了layout中variables与Views。如前面所讨论的,Binding的名称和包名可以定制。所生成的Binding类都扩展了android.databinding.ViewDataBinding
。
a)创建
Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate
方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater
还有一个是采用ViewGroup
:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果使用不同的机制载入layout,他可一分开绑定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil类来创建Binding:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
b)带ID的Views
在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用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>
它会生成如下的Binding类:
public final TextView firstName;
public final TextView lastName;
IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。
c)Variables
每个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>
它会在Binding中生成setters和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);
d)ViewStubs
ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。
由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy
对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。
当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy
必需监听ViewStub
的OnInflateListener
监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy
允许开发者在其上设置一个OnInflateListener
它会在建立Binding后调用。
e)Binding进阶
-
动态Variables
有时,不知道具体的Binding类,例如,一个
RecyclerView
适配器对layouts任意操作并不知道具体的Binding类。它仍然必需在onBindViewHolder
期间赋值给Binding。在这个例子中,该
RecyclerView
绑定的所有layouts有一个“item”的Variable。该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(); }
-
直接Binding
当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用
executePendingBindings()
方法。 -
后台线程
只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。
7)属性Setters
每当绑定值的变化,生成的Binding类必须调用setter方法。Data Binding框架有可以自定义赋值的方法。
a)自动Setters
对于一个属性,Data Binding试图找到setAttribute
方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。
例如,有关TextView的android:text
属性的表达式会寻找一个setText(String)
的方法。如果表达式返回一个int
,Data Binding会搜索的setText(int)
方法。注意:要表达式返回正确的类型,如果需要的话使用casting
。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout
没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
b)重命名的Setters
一些有setters的属性按名称并不匹配。对于这些方法,属性可以通过BindingMethods注解相关联。这必须与一个包含BindingMethod
注解的类相关联,每一个用于一个重命名的方法。例如,android:tint
属性与setImageTintList
相关联,而不与setTint
相关。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
以上例子,开发者需要重命名setters是不太可能了,android架构属性已经实现了。
c)自定义Setters
有些属性需要自定义绑定逻辑。例如,对于android:paddingLeft
属性并没有相关setter。相反,setPadding(left, top, right, bottom)
是存在在。一个带有BindingAdapter
注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。
Android的属性已经创造了BindingAdapters
。举例来说,对于paddingLeft
:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。
当有冲突时,开发人员创建的Binding适配器将覆盖Data Binding默认适配器。
您也可以创建可以接收多个参数的适配器。
@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}"/>
如果对于一个ImageViewimageUrl
和error都被使用并且imageUrl
是一个string类型以及error是一个drawable时,该适配器会被调用。
- 匹配的过程中自定义namespaces将被忽略。
- 你也可以为Android namespaces写适配器。
绑定适配器方法可以在处理程序中选择旧的值。一个取旧值和新值的方法应该具有所有这些属性的旧值,然后是新值:
@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.OnAttachStateChangeListener
相反的一组方法。android.databinding.adapters.ListenerUtil
类帮助跟踪以前的侦听器,以便在绑定Adaper中删除它们。
通过注解的接口OnViewDetachedFromWindow
和OnViewAttachedToWindow
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
,数据绑定代码生成器知道听众只能运行在蜂窝MR1时生成和新设备,支持的相同版本addOnAttachStateChangeListener(View.OnAttachStateChangeListener)
。
8)转换
a)对象转换
当从Binding表达式返回一个对象,一个setter会从自动、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。
这是为了方便那些使用ObservableMaps
来保存数据。例如:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在userMap
返回一个对象并且该对象将自动转换为setText(CharSequence)
的参数类型。当有关参数类型可能混乱时,开发人员需要在表达式中转换。
b)自定义转换
有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,背景需要Drawable
对象,但颜色是一个整数。不管何时有Drawable
并且返回值是一个整数,那么整数类型会被转换为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为数据绑定支持许多的代码编辑。例如,它支持以下功能:
- 语法高亮
- 标记表达式的语法错误
- XML代码补全
- 引用,包括navigation(如导航到声明处)以及快速文档查询
注意: 数组以及
通用类型
,比如说Observable
类,可能会显示错误事实上并没有错误。
预览面板会显示数据绑定的默认值。在以下例子中,面板会在TextView
中显示PLACEHOLDER
默认值
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
如果你需要在设计阶段就显示默认值,你可以使用工具属性来代替默认表达数值,参考:Designtime Layout Attributes
---------------------<完>-----------------------
原文链接:Data Binding Guide (Android)
(如有翻译有误或者不理解的地方,请指正)
网友评论