作者: weiyf
时间: 2016-10-31 21:35:33
原文链接:https://developer.android.com/topic/libraries/data-binding/index.html
欢迎访问我的博客查看更多的文章
这篇文档讲解了如何去使用Data Binding这个库来编写声明式布局和最大限度的减少绑定你应用逻辑和布局的“胶水代码”。
This document explains how to use the Data Binding Library to write declarative layouts and minimize the glue code necessary to bind your application logic and layouts.
Data Binding这个库提供了灵活性和广泛的兼容性 —— 它是一个support库,所以你可以在Android2.1(API level 7+)以上使用它。
The Data Binding Library offers both flexibility and broad compatibility — it's a support library, so you can use it with all Android platform versions back to Android 2.1 (API level 7+).
要使用data binding, Gradle的Android插件版本必须是1.5.0-alpha1以上。看如何升级Gradle的Android插件。
To use data binding, Android Plugin for Gradle 1.5.0-alpha1 or higher is required. See how to update the Android Plugin for Gradle.
构建环境(Build Environment)
要开始使用Data Binding, 需要在Android SDK manager中从Support repository下载最新的库。
To get started with Data Binding, download the library from the Support repository in the Android SDK manager.
配置你的app来使用data binding
, 在你的app module中的build.gradle
里面添加dataBinding标签。
To configure your app to use data binding, add the dataBinding element to your build.gradle file in the app module.
使用下面的代码片段来配置data binding:
Use the following code snippet to configure data binding:
android {
....
dataBinding {
enabled = true
}
}
如果你有一个依赖于一个使用data binding的library的app module, 你的app module也一样必须在它的build.gradle
中配置data binding。
If you have an app module that depends on a library which uses data binding, your app module must configure data binding in its build.gradle file as well.
同时,确保你在使用兼容了data binding的Android Studio。在Android Studio 1.3或者更高版本提供了支持对data binding的支持,详见Android Studio Support for Data Binding。
Also, make sure you are using a compatible version of Android Studio. Android Studio 1.3 and later provides support for data binding as described in Android Studio Support for Data Binding.
数据绑定的布局文件(Data Binding Layout Files)
编写你的第一个data binding表达式集合(Writing your first set of data binding expressions)
Data-binding布局文件和普通的略有不同,它是以layout标签作为根节点,紧跟着里面是data标签和view的根标签。这个view标签是你之前没有使用data-binding的布局文件的跟标签。就像下面的这个例子:
Data-binding layout files are slightly different and start with a root tag of layout followed by a data element and a view root element. This view element is what your root would be in a non-binding layout file. A sample file looks like this:
在data标签中的user变量描述了一个即将会在这个布局中用到的属性。
The user variable within data describes a property that may be used within this layout.
数据对象(Data Object)
让我们假设现在我们有一个plain-old Java object(POJO) User对象:
Let's assume for now that you have a plain-old Java object (POJO) for 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对象。
This type of object has data that never changes. It is common in applications to have data that is read once and never changes thereafter. It is also possible to use a JavaBeans objects:
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
的这个方法。此外,如果firstName()
方法存在的情况下也同样可用。
From the perspective of data binding, these two classes are equivalent. The expression @{user.firstName} used for the TextView's android:text attribute will access the firstName field in the former class and the getFirstName() method in the latter class. Alternatively, it will also be resolved to firstName() if that method exists.
绑定数据(Binding Data)
在默认情况下,一个绑定类是基于布局文件的名字生成的,将布局文件的名字转换为“Pascal”命名(也就是首字母大写)并在后面加"Binding"。上面的布局文件名字为main_activity
,所以生成的类的名字为MainActivityBinding
。这个类包含了所有的有关于这个布局文件中Views的属性的绑定关系。(e.g.user变量),而且它还知道如何给binding表达式分配值。创建bindings最简单的方法是inflating:
By default, a Binding class will be generated based on the name of the layout file, converting it to Pascal case and suffixing "Binding" to it. The above layout file was main_activity.xml so the generate class was MainActivityBinding. This class holds all the bindings from the layout properties (e.g. the user variable) to the layout's Views and knows how to assign values for the binding expressions.The easiest means for creating the bindings is to do it while 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);
}
就是这样!运行程序然后你就会在UI上看到测试User。另外,你也可以这样得到view:
You're done! Run the application and you'll see Test User in the UI. Alternatively, you can get the view via:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在ListView或者RecyclerView的adapter中的item使用data binding, 你可能更愿意这样做:
If you are using data binding items inside a ListView or RecyclerView adapter, you may prefer to use:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件处理(Event Handling)
Data Binding 允许你编写表达式来处理事件那些从view调度的事件(e.g.onClick)。事件的属性名称会被listener的方法名字限制,但也有例外的情况。例如,View.OnLongClickListener有一个[onLongClick()](https://developer.android.com/reference/android/view/View.OnLongClickListener.html#onLongClick(android.view.View)的方法,所以这个事件的属性为android:onLongClick
。这里就有两种方法来处理这个事件。
Data Binding allows you to write expressions handling events that are dispatched from the views (e.g. onClick). Event attribute names are governed by the name of the listener method with a few exceptions. For example, View.OnLongClickListener has a method onLongClick(), so the attribute for this event is android:onLongClick. There are two ways to handle an event.
- 方法引用:在你的表达式中,你可以参考符合监听器方法这种模式的方法。当一个表达式的计算结果为一个方法的引用,Data Binding 会包装方法的引用和监听器里的对象,然后在那个目标view设置那个监听器。如果表达式的计算结果为空,Data Binding就不会创建监听器,然后设置一个空的监听器来代替。
- Method References: In your expressions, you can reference methods that conform to the signature of the listener method. When an expression evaluates to a method reference, Data Binding wraps the method reference and owner object in a listener, and sets that listener on the target view. If the expression evaluates to null, Data Binding does not create a listener and sets a null listener instead.
- 监听绑定:这些是当事件发生的时候的lambda表达式。Data Binding总会在view上创建一个监听器。当事件被调度的时候,这个listener就会求这个lambda表达式的值。
- Listener Bindings: These are lambda expressions that are evaluated when the event happens. Data Binding always creates a listener, which it sets on the view. When the event is dispatched, the listener evaluates the lambda expression.
方法引用(Method References)
事件可以直接绑定在处理方法上,就像android:onClick
的方式可以分配给一个Activity中的一个方法。相比于View#onClick
属性有一个主要的优点,那就是表达式是在编译期进行处理的,所以如果那个方法不存在或者它的写法不正确,你会在编译期出现编译时错误。
Events can be bound to handler methods directly, similar to the way android:onClick can be assigned to a method in an Activity. One major advantage compared to the View#onClick attribute is that the expression is processed at compile time, so if the method does not exist or its signature is not correct, you receive a compile time error.
方法引用和监听器绑定两者之间的主要不同是实际的监听器实现是创建在数据已经绑定之后,不是在事件被调用的时候。如果你喜欢在事件发生的时候去计算表达式的值,那你就需要使用监听器绑定。
The major difference between Method References and Listener Bindings is that the actual listener implementation is created when the data is bound, not when the event is triggered. If you prefer to evaluate the expression when the event happens, you should use listener binding.
将事件分配给它的handler,请使用一个正常的且它的值是要调用的方法的名称的绑定表达式。例如,如果你的数据对象有两个方法:
To assign an event to its handler, use a normal binding expression, with the value being the method name to call. For example, if your data object has two methods:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
这个绑定表达式可以分配一个点击监听器给一个view:
The binding expression can assign the click listener for a View:
需要注意的是,在表达式中的方法的签名必须与监听器对象里的方法签名一致。
Note that the signature of the method in the expression must exactly match the signature of the method in the Listener object.
监听绑定(Listener Bindings)
监听绑定是当一个事件发生时候的表达式。他们就是方法引用,但是它们允许你运行任意的data binding表达式。这个特性是在Android Gradle插件2.0及以后才可用。
Listener Bindings are binding expressions that run when an event happens. They are similar to method references, but they let you run arbitrary data binding expressions. This feature is available with Android Gradle Plugin for Gradle version 2.0 and later.
在方法引用,方法参数必须与事件监听器的参数相匹配。但是在监听绑定中,只需要返回的值与监听器的表达式返回的预期的值相匹配就可以(除非预期返回的是void)。举个例子,你可以有一个含有一下方法的presenter的类:
In method references, the parameters of the method must match the parameters of the event listener. In Listener Bindings, only your return value must match the expected return value of the listener (unless it is expecting void). For example, you can have a presenter class that has the following method:
public class Presenter {
public void onSaveClick(Task task){}
}
然后你就可以你的类绑定这个点击事件,如下所示:
Then you can bind the click event to your class as follows:
监听器用只允许作为你的表达式的根元素的lambda表达式表示。当在表达式中使用回调时,Data Binding自动的创建所必须的监听器和注册这个事件。当view触发这个事件的时候,Data Binding就会计算给定的表达式。在正则binding表达式中,当这些监听器表达式被计算的时候,依然会得到null和出现Data Binding的线程安全问题。
Listeners are represented by lambda expressions that are allowed only as root elements of your expressions. When a callback is used in an expression, Data Binding automatically creates the necessary listener and registers for the event. When the view fires the event, Data Binding evaluates the given expression. As in regular binding expressions, you still get the null and thread safety of Data Binding while these listener expressions are being evaluated.
需要注意上面的例子,我们没有去声明view这个传到onClick(android.view.View)的里的参数。监听binding提供两种选择给监听器的参数:你完全可以忽略方法中的所有的参数或者重命名所有参数。如果你准备去重命名这些参数,你可以在你的表达式中使用这些参数。举个例子,上面的表达式可以这样写:
Note that in the example above, we haven't defined the view parameter that is passed into onClick(android.view.View). Listener bindings provide two choices for listener parameters: you can either ignore all parameters to the method or name all of them. If you prefer to name the parameters, you can use them in your expression. For example, the expression above could be written as:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者如果你想在你的表达式中使用这些参数,它们就像如下这样工作:
Or if you wanted to use the parameter in the expression, it could work as follows:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
当多于一个参数的的时候,你可以使用lambda表达式
You can use a lambda expression with more than one parameter:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
如果你监听的事件返回一个非void
的值,你的表达式也必须返回相同类类型的值。举个例子:如果你想要去监听长按这个事件,你的表达式就需要返回一个boolean值。
If the event you are listening to returns a value whose type is not void, your expressions must return the same type of value as well. For example, if you want to listen for the long click event, your expression should return boolean.
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于空对象导致无法计算表达式,Data Binding会返回Java类型默认的值。举个例子,引用类型会返回null, int会返回0,boolean会返回false,如此类推。
If the expression cannot be evaluated due to null objects, Data Binding returns the default Java value for that type. For example, null for reference types, 0 for int, false for boolean, etc.
如果你需要使用一个带有断言的表达式(e.g.三元表达式),你可以使用void:
If you need to use an expression with a predicate (e.g. ternary), you can use void as a symbol.
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免复杂的监听器(Avoid Complex Listeners)
监听器表达式是非常强大的并且可以使你的代码更容易阅读。在另一方面,监听器包含复杂的表达式会导致你的布局难以去阅读和变得不可维护。这些表达式需要和将可用数据从UI传递到回调方法那样简单。您应该在从侦听器表达式调用的回调方法中实现任何业务逻辑。
Listener expressions are very powerful and can make your code very easy to read. On the other hand, listeners containing complex expressions make your layouts hard to read and unmaintainable. These expressions should be as simple as passing available data from your UI to your callback method. You should implement any business logic inside the callback method that you invoked from the listener expression.
存在一些专门的点击事件处理,并且他们需要一个android:onClick
以外的属性来避免冲突。已创建以下属性以避免此类冲突:
Some specialized click event handlers exist and they need an attribute other than android:onClick to avoid a conflict. The following attributes have been created to avoid such conflicts:
类 | Listener Setter | 属性 |
---|---|---|
SearchView | [setOnSearchClickListener(View.OnClickListener)](https://developer.android.com/reference/android/widget/SearchView.html#setOnSearchClickListener(android.view.View.OnClickListener) | android:onSearchClick |
ZoomControls | [setOnZoomInClickListener(View.OnClickListener)](https://developer.android.com/reference/android/widget/ZoomControls.html#setOnZoomInClickListener(android.view.View.OnClickListener) | android:onZoomIn |
ZoomControls | [setOnZoomOutClickListener(View.OnClickListener)](https://developer.android.com/reference/android/widget/ZoomControls.html#setOnZoomOutClickListener(android.view.View.OnClickListener) | android:onZoomOut |
布局细节(Layout Details)
导入(Imports)
在data标签中可以使用零个或者多个import标签。这使得你可以在布局文件中轻松的引用类。就像java一样。
Zero or more import elements may be used inside the data element. These allow easy reference to classes inside your layout file, just like in Java.
现在,你可以在view里面使用binding表达式:
Now, View may be used within your binding expression:
当有类名冲突的时候,其中一个类可以重命名为一个别名。
When there are class name conflicts, one of the classes may be renamed to an "alias:"
现在,在这个布局中,Vista可以用于com.example..real.estate.View
的引用和View可以用于android.view.View
的引用。导入类型可以用作变量和表达式中的类型引用:
Now, Vista may be used to reference the com.example.real.estate.View and View may be used to reference android.view.View within the layout file. Imported types may be used as type references in variables and expressions:
Android Sutdio 没有对导入做处理,所以导入变量的自动补全功能在你的IDE上面是无法工作的。你的应用程序将仍然编译通过,在你声明变量的时候,你可以通过使用变量的完整的包名来解决这个IDE的问题。
Note: Android Studio does not yet handle imports so the autocomplete for imported variables may not work in your IDE. Your application will still compile fine and you can work around the IDE issue by using fully qualified names in your variable definitions.
在引用表达式中使用静态变量和静态方法时,也可以导入类型使用。
Imported types may also be used when referencing static fields and methods in expressions:
…
就像在Java中一样,java.lang.*
是自动导入的。
Just as in Java, java.lang.* is imported automatically.
变量(Variables)
可以在data标签内部使用任意数量的variable标签。在布局文件上,每一个variable标签描述了一个可以在布局文件中binding表达式中使用的变量的属性。
Any number of variable elements may be used inside the data element. Each variable element describes a property that may be set on the layout to be used in binding expressions within the layout file.
在编译期会检查变量的类型,所以如果一个变量实现了Observable或者是一个Observable集合,那么应该反应在类型中。如果这个变量是一个没有实现Observable*
接口的基类或者接口,这个变量将不会被observed。
The variable types are inspected at compile time, so if a variable implements Observable or is an observable collection, that should be reflected in the type. If the variable is a base class or interface that does not implement the Observable* interface, the variables will not be observed!
当针对不同的配置有不同的布局文件的时候(e.g.横屏和竖屏),这些变量会被合并。所以这些布局文件之间不能声明有冲突的变量。
When there are different layout files for various configurations (e.g. landscape or portrait), the variables will be combined. There must not be conflicting variable definitions between these layout files.
生成的binding类中每一个声明的变量会有一个setter和getter。这些变量会使用java的默认值直到它的setter被调用 —— 引用类型为null, int为0,boolean为false,如此类推。
The generated binding class will have a setter and getter for each of the described variables. The variables will take the default Java values until the setter is called — null for reference types, 0 for int, false for boolean, etc.
将生成一个名为context
的特殊变量,以根据需要用于绑定表达式。context
的值是来自根视图的[getContext](https://developer.android.com/reference/android/view/View.html#getContext()方法返回的Context
.contenxt
变量会被同名的显示变量覆盖。
A special variable named context is generated for use in binding expressions as needed. The value for context is the Context from the root View's getContext(). The context variable will be overridden by an explicit variable declaration with that name.
自定义Binding类名(Custom Binding Class Names)
在默认情况下,一个Binding类是基于它的布局文件的名字生成的,由大写字母开头,去掉无用的字符( _ ),大写首字母然后在后面加上“Binding”后缀。这个类会被放在module模块下的databinding包下。举个例子,一个名为contact_item.xml
的布局文件会生成ContactItemBinding
这样的一个类。如果module模块的包名为com.example.my.app
,然后就会被放在com.example.my.app.databinding
下。
By default, a Binding class is generated based on the name of the layout file, starting it with upper-case, removing underscores ( _ ) and capitalizing the following letter and then suffixing "Binding". This class will be placed in a databinding package under the module package. For example, the layout file contact_item.xml will generate ContactItemBinding. If the module package is com.example.my.app, then it will be placed in com.example.my.app.databinding.
通过调整data标签的class
属性,Binding类可以被重命名或者放置在不同的包中。
Binding classes may be renamed or placed in different packages by adjusting the class attribute of the data element. For example:
...
这将在module模块下的databinding包下生成ContactItem Binding类。如果这个类需要在module模块下不同包名下生成,它可以使用"."作为前缀。
This generates the binding class as ContactItem in the databinding package in the module package. If the class should be generated in a different package within the module package, it may be prefixed with ".":
...
在这种情况下,ContactItem
是在module模块夏直接生成的。如果提供完整的包名,则可以使用任何包名:
In this case, ContactItem is generated in the module package directly. Any package may be used if the full package is provided:
...
Includes
通过使用应用命名空间以及在属性中的变量名字从容器布局中传递变量到一个被include的布局中:
Variables may be passed into an included layout's binding from the containing layout by using the application namespace and the variable name in an attribute:
这里,必须在name.xml
和contact.xml
中都有一个user变量。
Here, there must be a user variable in both the name.xml and contact.xml layout files.
Data binding 不支持include一个直接使用merge
标签的布局。举个例子:下面这个布局是不支持的:
Data binding does not support include as a direct child of a merge element. For example, the following layout is not supported:
表达式语言(Expression Language)
共同特征(Common Features)
表达式语言看起来很像java的表达式。这些都是一样的:
The expression language looks a lot like a Java expression. These are the same:
- 数学计算(Mathematical)
+ - / * %
- 字符串连接(String concatenation)
+
- 逻辑(Logical)
&& ||
- 二进制(Binary)
& | ^
- 一元(Unary)
+ - ! ~
- 位移(Shift)
>> >>> <<
- 比较(Comparison)
== > < >= <=
instanceof
- 分组(Grouping)
()
- 文字-字符(Literals - character),字符串(String),数字(numeric),
null
- 类型转换(Cast)
- 方法调用(Method calls)
- 域读取(Field access)
- 数组读取(Array access)
[]
- 三元(Ternary operator)
?:
例子:
Examples:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
缺失的操作符(Missing Operations)
缺失一些你可以在java中使用的表达式语法。
A few operations are missing from the expression syntax that you can use in Java.
this
super
new
- 显示泛型调用(Explicit generic invocation)
空合并操作符(Null Coalescing Operator)
空合并操作符(??)当左边不为空的时候选择左边,否则选择右边。
The null coalescing operator (??) chooses the left operand if it is not null or the right if it is null.
android:text="@{user.displayName ?? user.lastName}"
在功能上等同于:
This is functionally equivalent to:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用(Property Reference)
第一个我们在上面已经讨论过了编写你的第一个data binding表达式:JavaBean引用的简短格式。当一个表达式引用一个类的属性,它仍使用同样的格式对于字段,getters以及ObservableFields。
The first was already discussed in the Writing your first data binding expressions above: short form JavaBean references. When an expression references a property on a class, it uses the same format for fields, getters, and ObservableFields.
android:text="@{user.lastName}"
避免空指针(Avoiding NullPointerException)
生成的data binding代码自动的检查空和避免空指针异常。举个例子,在@{user.name}
表达式中,如果user是null,user.name会被制定它的默认值(null)。如果你是引用user.age
,age是一个int类型,所以它是默认值0;
Generated data binding code automatically checks for nulls and avoid null pointer exceptions. For example, in the expression @{user.name}, if user is null, user.name will be assigned its default value (null). If you were referencing user.age, where age is an int, then it would default to 0.
集合(Collections)
常用集合:arrays,lists,sparse lists, 可以使用[]运算符来访问。
Common collections: arrays, lists, sparse lists, and maps, may be accessed using the [] operator for convenience.
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
字符串(String Literals)
挡在属性值周围使用单引号时,你就可以在表达式中使用双引号:
When using single quotes around the attribute value, it is easy to use double quotes in the expression:
android:text='@{map["firstName"]}'
也可以使用双引号来包围属性值。当这样做时,字符串应该使用'或者后引号(`)。
It is also possible to use double quotes to surround the attribute value. When doing so, String literals should either use the ' or back quote (`).
android:text="@{map[`firstName`]}"
android:text="@{map['firstName']}"
资源(Resources)
可以使用正常的语法作为表达式的一部分来访问资源。
It is possible to access resources as part of expressions using the normal syntax:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和复数可以通过提供参数来计算:
Format strings and plurals may be evaluated by providing parameters:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当一个复数需要多个参数时,所有的参数都应该被传递:
When a plural takes multiple parameters, all parameters should be passed:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
一些资源需要显示类型计算。
Some resources require explicit type evaluation.
类型 | 正常引用 | 表达式引用 |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArry | @array | @typeArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
Data objects
任何纯java对象(POJO)可以用于data binding,但是修改一个POJO将不会导致UI的更新。Databinding的强大之处在于它可以让你的数据拥有更新通知的能力。有三种不同的数据变动通知机制,Observable object,observable fields和observable collections
Any plain old Java object (POJO) may be used for data binding, but modifying a POJO will not cause the UI to update. The real power of data binding can be used by giving your data objects the ability to notify when data changes. There are three different data change notification mechanisms, Observable objects, observable fields, and observable collections.
当这些可观察数据对象绑定在UI,data对象的一个属性改变了,UI就会马上被更新。
When one of these observable data object is bound to the UI and a property of the data object changes, the UI will be updated automatically.
可观察对象(Observable Objects)
一个实现了Observable接口的类会允许binding将单个监听器附加到绑定对象,以监听该对象上的所有属性的更改。
A class implementing the Observable interface will allow the binding to attach a single listener to a bound object to listen for changes of all properties on that object.
Observable接口拥有添加或者移除监听器机制,但是通知由开发人员管理。为了使开发更容易,创建了一个基类BaseObservable以实现监听器注册机制。数据类实现者仍然负责通知属性何时更改。这是通过添加一个Bindable注解到getter并通知setter。
The Observable interface has a mechanism to add and remove listeners, but notifying is up to the developer. To make development easier, a base class, BaseObservable, was created to implement the listener registration mechanism. The data class implementer is still responsible for notifying when the properties change. This is done by assigning a Bindable annotation to the getter and notifying in the 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来实现,以有限地存储和通知监听器。
The Bindable annotation generates an entry in the BR class file during compilation. The BR class file will be generated in the module package. If the base class for data classes cannot be changed, the Observable interface may be implemented using the convenient PropertyChangeRegistry to store and notify listeners efficiently.
可观察域(ObservableFields)
创建Observable需要做一些工作,所以开发者想要节省时间或者少一些属性可以使用ObservableField或者它的派生ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable。ObservableField
是自包含具有单个字段的observable对象。原始版本避免访问操作期间的装箱和拆箱。要使用,请在数据类中创建public final字段:
A little work is involved in creating Observable classes, so developers who want to save time or have few properties may use ObservableField and its siblings ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable. ObservableFields are self-contained observable objects that have a single field. The primitive versions avoid boxing and unboxing during access operations. To use, create a public final field in the data class:
private static class User {
public final ObservableField firstName =
new ObservableField<>();
public final ObservableField lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
是的!要访问该值,请使用set和get存取器方法:
That's it! To access the value, use the set and get accessor methods:
user.firstName.set("Google");
int age = user.age.get();
可观察集合(Observable Collections)
一些应用程序使用更多的动态结构来保存数据。可观察集合允许用键对这些数据对象进行访问。当ObservableArrayMap的key是引用类型的时候是很有用的,例如String。
Some applications use more dynamic structures to hold data. Observable collections allow keyed access to these data objects. ObservableArrayMap is useful when the key is a reference type, such as String.
ObservableArrayMap user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以通过字符串键来访问map:
In the layout, the map may be accessed through the String keys:
…
当ObservableArrayList的键是一个integer的时候是很有用的:
ObservableArrayList is useful when the key is an integer:
ObservableArrayList
在布局中,列表可以通过索引来访问:
In the layout, the list may be accessed through the indices:
…
生成Binding(Generated Binding)
生成的binding类将布局变量与布局中的视图相关联。如前所述,binding的名称和包名都可以定制。生成的Binding类度继承了ViewDataBinding。
The generated binding class links the layout variables with the Views within the layout. As discussed earlier, the name and package of the Binding may be customized. The Generated binding classes all extend ViewDataBinding.
创建(Creating)
Binding应该在inflate之后就立刻创建,以确保在使用布局中的表达式绑定到视图之前view的层次结构不会被干扰。有几种方法绑定到布局。最常见的是在Binding类上使用静态方法。inflate方法inflatesview的层次结构并绑定它是一步到位的。有一个简单的版本,只需要一个LayoutInflater,还有一个是ViewGroup:
The binding should be created soon after inflation to ensure that the View hierarchy is not disturbed prior to binding to the Views with expressions within the layout. There are a few ways to bind to a layout. The most common is to use the static methods on the Binding class.The inflate method inflates the View hierarchy and binds to it all it one step. There is a simpler version that only takes a LayoutInflater and one that takes a ViewGroup as well:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局是采用不同的机制inflated的,它可以单独绑定:
If the layout was inflated using a different mechanism, it may be bound separately:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有些时候,Binding并不能提前知道。在这种情况下,可以使用DataBindingUtil类来创建binding:
Sometimes the binding cannot be known in advance. In such cases, the binding can be created using the DataBindingUtil class:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
带有ID的View(Views With IDs)
将在布局中为每个具有ID的View生成public final字段。binding在View层次结构上执行单次传递,从而使用ID提取View。这种机制可能比为几个view调用findViewById
更快。例如:
A public final field will be generated for each View with an ID in the layout. The binding does a single pass on the View hierarchy, extracting the Views with IDs. This mechanism can be faster than calling findViewById for several Views. For example:
将在Binding类生成:
Will generate a binding class with:
public final TextView firstName;
public final TextView lastName;
IDs在data binding中几乎没有必要,但是仍然有一些情况下需要冲代码访问View。
IDs are not nearly as necessary as without data binding, but there are still some instances where access to Views are still necessary from code.
变量(Variables)
每一个变量都会有相应的访问方法。
Each variable will be given accessor methods.
将在绑定中生成setter和getter:
will generate setters and getters in the binding:
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);
ViewStubs
ViewStubs跟正常的Views有点点不一样。它们开始时是不可见的,当它们要设置为可见或被明确告知要inflate时,它们通过inflate另外一个布局来取代自己。
ViewStubs are a little different from normal Views. They start off invisible and when they either are made visible or are explicitly told to inflate, they replace themselves in the layout by inflating another layout.
因为ViewStub基本上从View的层次结构中消失,视图中的binding对象也必须要消失来允许回收。因为那个Views是final的,一个ViewStubProxy对象代替了ViewStub,使得开发人员可以在ViewStub存在时访问它,还可以在ViewStub已经inflate时访问inflate的View层次结构。
Because the ViewStub essentially disappears from the View hierarchy, the View in the binding object must also disappear to allow collection. Because the Views are final, a ViewStubProxy object takes the place of the ViewStub, giving the developer access to the ViewStub when it exists and also access to the inflated View hierarchy when the ViewStub has been inflated.
当inflating另外的布局,必须为新布局建立binding。因此,ViewStubProxy必须监听ViewStub的ViewStub.OnInflateListener和在那时建立binding。因为只有一个可以存在,ViewStubProxy允许开发人员在建立binding之后调用OnInflateListener
。
When inflating another layout, a binding must be established for the new layout. Therefore, the ViewStubProxy must listen to the ViewStub's ViewStub.OnInflateListener and establish the binding at that time. Since only one can exist, the ViewStubProxy allows the developer to set an OnInflateListener on it that it will call after establishing the binding.
高级绑定(Advanced Binding)
动态变量(Dynamic Variables)
有时,不知道特定的binding类。例如,一个RecyclerView.Adapter对任意布局操作不会知道具体的binding类。它仍然必须在[onBindViewHolder(VH, int)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onBindViewHolder(VH, int))期间分配binding值。
At times, the specific binding class won't be known. For example, a RecyclerView.Adapter operating against arbitrary layouts won't know the specific binding class. It still must assign the binding value during the onBindViewHolder(VH, int).
在这个例子中,RecyclerView绑定的所有的布局度有一个"item"的变量。BindingHolder有一个getBinding方法返回ViewDataBinding。
In this example, all layouts that the RecyclerView binds to have an "item" variable. The BindingHolder has a getBinding method returning the ViewDataBinding base.
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
即时绑定(Immediate Binding)
当一个变量或者observable发生变化的时候,binding将被安排在下一帧之前进行更改。但是有时必须立即执行binding。可以使用[executePendingBindings()](https://developer.android.com/reference/android/databinding/ViewDataBinding.html#executePendingBindings()方法强制执行。
When a variable or observable changes, the binding will be scheduled to change before the next frame. There are times, however, when binding must be executed immediately. To force execution, use the executePendingBindings() method.
后台线程(Background Thread)
只要你的数据模式不是一个集合,你就可以在后台线程中更改它。data binding将本地化每一个变量/字段,同时计算以避免任何并发问题。
You can change your data model in a background thread as long as it is not a collection. Data binding will localize each variable / field while evaluating to avoid any concurrency issues.
属性Setters(Attribute Setters)
每当绑定的值发生变化时,生成的binding类必须在具有binding表达式的view上调用setter方法。data binding框架有自定义哪个方法来调用以设置值的方法。
Whenever a bound value changes, the generated binding class must call a setter method on the View with the binding expression. The data binding framework has ways to customize which method to call to set the value.
自定Setters(Automatic Setters)
对于属性,data binding视图寻找方法来设置属性值。属性的命名空间无关紧要,仅仅只与属性的名称有关。
For an attribute, data binding tries to find the method setAttribute. The namespace for the attribute does not matter, only the attribute name itself.
例如,一个表达式关联了TextView的属性android:text
,会去寻找setText(String)方法。如果表达式返回一个int类型的值,data binding将会去寻找一个setText(int)的方法。需要注意的是让变大时返回正确的类型。如果需要则强制转换。请注意,即使没有给定名称的属性,Databinding也会照常工作。然后,你可以通过使用data binding轻松为任何的setter创建属性。例如,support包的DrawerLayout没有任何属性,但含有大量的setter。你可以使用自动setters来使用其中的某一个。
For example, an expression associated with TextView's attribute android:text will look for a setText(String). If the expression returns an int, data binding will search for a setText(int) method. Be careful to have the expression return the correct type, casting if necessary. Note that data binding will work even if no attribute exists with the given name. You can then easily "create" attributes for any setter by using data binding. For example, support DrawerLayout doesn't have any attributes, but plenty of setters. You can use the automatic setters to use one of these.
重命名Setters(Renamed Setters)
某些属性具有与名称不匹配的setter。对于这些方法,属性可以通过BindingMethods注解与setter相关联。这必须与一个包含BindingMethod注解类相关联,它们每一个用于重命名的方法。例如,android:tint
属性实际上是与setImageTintList(ColorStateList)相关联,而不是setTint
。
Some attributes have setters that don't match by name. For these methods, an attribute may be associated with the setter through BindingMethods annotation. This must be associated with a class and contains BindingMethod annotations, one for each renamed method. For example, the android:tint attribute is really associated with setImageTintList(ColorStateList), not setTint.
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
开发人员不太可能需要重命名setter, android框架的属性已经实现了。
It is unlikely that developers will need to rename setters; the android framework attributes have already been implemented.
自定义Setters(Custom Setters)
有写属性需要自定义Binding逻辑,例如:没有为android:paddingLeft
属性关联setter。相反,setPadding(left, top, right, bottom)
是存在的。使用BindingAdapter注解的静态binding设配器方法允许开发人员以定制如何调用属性的setter。
Some attributes need custom binding logic. For example, there is no associated setter for the android:paddingLeft attribute. Instead, setPadding(left, top, right, bottom) exists. A static binding adapter method with the BindingAdapter annotation allows the developer to customize how a setter for an attribute is called.
android属性已经创建了BindingAdapter,举个例子,对于paddingLeft
:
The android attributes have already had BindingAdapters created. For example, here is the one for paddingLeft:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
BindingAdapter可以用于其他类型的定制。例如,自定义的loader可以用于异步加载图像。
Binding adapters are useful for other types of customization. For example, a custom loader can be called off-thread to load an image.
当发生冲突时,开发人员创建的BindingAdapter将覆盖默认的data binding adapters。
Developer-created binding adapters will override the data binding default adapters when there is a conflict.
你也可以创建可以接收多个参数的适配器。
You can also have adapters that receive multiple parameters.
@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 imageUrl和error都被使用,并且imageUrl是一个string类型以及error是一个drawable时,该适配器会被调用。
This adapter will be called if both imageUrl and error are used for an ImageView and imageUrl is a string and error is a drawable.
- 在匹配过程中自定义的命名空间会被忽略。
- 你也可以为android的命名空间编写设配器。
- Custom namespaces are ignored during matching.
- You can also write adapters for android namespace.
Binding adapter方法可以可选的在handler获取旧值。采用旧值和新值的方法应该具有属性的所有旧值并放在前面,然后紧接着是新值。
Binding adapter methods may optionally take the old values in their handlers. A method taking old and new values should have all old values for the attributes come first, followed by the new values:
@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());
}
}
事件处理程序只能与一个抽象方法的接口或者抽象类一起使用。例如:
Event handlers may only be used with interfaces or abstract classes with one abstract method. For example:
@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);
}
}
}
当一个监听器含有多个方法时t,它必须拆分成多个监听器。例如,View.OnAttachStateChangeListener含有两个方法:[onViewAttachedToWindow()](https://developer.android.com/reference/android/view/View.OnAttachStateChangeListener.html#onViewAttachedToWindow(android.view.View)和[onViewDetachedFromWindow()](https://developer.android.com/reference/android/view/View.OnAttachStateChangeListener.html#onViewDetachedFromWindow(android.view.View)。然后,我们必须创建两个接口来区分它们的属性和处理程序。
When a listener has multiple methods, it must be split into multiple listeners. For example, View.OnAttachStateChangeListener has two methods: onViewAttachedToWindow() and onViewDetachedFromWindow(). We must then create two interfaces to differentiate the attributes and handlers for them.
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因为改变一个监听器也会影响另一个监听器,所以我们必须有三个不同的绑定适配器,如果它们都被设置,其中两个用于每个属性,一个用于两个属性。
Because changing one listener will also affect the other, we must have three different binding adapters, one for each attribute and one for both, should they both be set.
@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
类可以帮助跟踪以前的监听器,以便他们可以在在Binding Adapter中删除。
The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.
通过用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
注释接口OnViewDetachedFromWindow
和OnViewAttachedToWindow
,data binding代码生成器会知道这些监听器只能在Honeycomb MR1或者更新的设备(与addOnAttachStateChangeListener(View.OnAttachStateChangeListener)支持的版本相同)上使用。
By annotating the interfaces OnViewDetachedFromWindow and OnViewAttachedToWindow with @TargetApi(VERSION_CODES.HONEYCOMB_MR1), the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).
转换器(Converters)
对象转换器(Object Conversions)
当从Binding表达式返回一个对象,一个setter会从自动、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。
When an Object is returned from a binding expression, a setter will be chosen from the automatic, renamed, and custom setters. The Object will be cast to a parameter type of the chosen setter.
这是一个使用ObservableMaps来保存数据的例子:
This is a convenience for those using ObservableMaps to hold data. for example:
userMap返回一个对象,这个对象将会被自动的转换为找到的settersetText(CharSequence)
的参数类型。当参数类型可能存在混淆时,开发者需要在表达式中手动做类型转换。
The userMap returns an Object and that Object will be automatically cast to parameter type found in the setter setText(CharSequence). When there may be confusion about the parameter type, the developer will need to cast in the expression.
自定义转换器(Custom Conversions)
有些时候,转换应在特定的类型之间自动进行。例如当设置背景的时候:
Sometimes conversions should be automatic between specific types. For example, when setting the background:
这里,背景使用了一个Drawable,但是颜色是一个integer,每当一个Drawable被期望并返回一个integer,int应该被转换为ColorDrawable。这种转换是使用带有BindingConversion注解的静态方法完成的:
Here, the background takes a Drawable, but the color is an integer. Whenever a Drawable is expected and an integer is returned, the int should be converted to a ColorDrawable. This conversion is done using a static method with a BindingConversion annotation:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
需要注意的是,转换只发生在setter级别,因此不允许混合这样的类型:
Note that conversions only happen at the setter level, so it is not allowed to mix types like this:
Android Studio对Data Binding的支持(Android Studio Support for Data Binding)
Android Studio支持许多用于数据绑定代码的代码编辑功能。例如,它支持数据绑定表达式的以下功能:
Android Studio supports many of the code editing features for data binding code. For example, it supports the following features for data binding expressions:
- 语法高亮显示
- 标记表达式语言语法错误
- XML代码完成
- 引用,包括导航(如导航到声明)和快速文档。
- Syntax highlighting
- Flagging of expression language syntax errors
- XML code completion
- References, including navigation (such as navigate to a declaration) and quick documentation
注意:数组和泛型类型如Observable类可能在没有错误时显示错误。
Note: Arrays and a generic type, such as the Observable class, might display errors when there are no errors.
“预览”窗格显示数据绑定表达式的默认值(如果提供)。在以下示例中,从布局XML文件中截取元素,“预览”窗格在TextView中显示PLACEHOLDER默认文本值。
The Preview pane displays default values for data binding expressions if provided. In the following example excerpt of an element from a layout XML file, the Preview pane displays the PLACEHOLDER default text value in the TextView.
如果需要在项目的设计阶段显示默认值,还可以使用工具属性而不是默认表达式值,如Designtime布局属性中所述。
If you need to display a default value during the design phase of your project, you can also use tools attributes instead of default expression values, as described in Designtime Layout Attributes.
转载请注明出处:http://weiyf.cn/2016/10/31/DataBinding——data-binding-guide/