Google Data Binding Library官方文档[原文地址](https://developer.android.google.cn/topic/libraries/data-binding/index.html)
#Data Binding Library 数据绑定库
本文档解释了如何使用Data Binding Library编写声明式布局,并尽量减少绑定应用程序逻辑和布局所需的胶合代码。
Data Binding Library提供了灵活性和广泛的兼容性 - 它是一个支持库,所以您可以将它与所有Android平台版本一起使用回到Android 2.1(API级别7+)。
使用DataBinding要求Gradle插件为1.5.0-alpha1及以上版本。[查看如何升级Gradle插件](https://developer.android.google.cn/studio/releases/gradle-plugin.html#updating-plugin)。
#Build Environment 构建环境
使用Data Binding之前,需要用Android SDK manager 在Support repository中下载依赖库。
配置app使用data binding,在app模块下的build.gradle文件中添加dataBinding元素。
```
android {
....
dataBinding {
enabled = true
}
}
```
如果你的app模块依赖的library使用了data binding,你的app模块也必须在它的build.gradle文件中配置data binding。
同时,确定你使用的是兼容版本的Android Studio,Android Studio 1.3及更高版本支持data binding ,如[Android Studio Support for Data Binding](https://developer.android.google.cn/topic/libraries/data-binding/index.html#studio_support)中所说的那样。
#Data Binding Compiler V2 数据绑定编译器V2
Android Gradle Plugin 3.1.0 Canary 6 附带了一个可选的新编译器。要开始使用它,更新你的gradle.properties 文件使它包含下面这行内容
```
android.databinding.enableV2=true
```
在编译器V2中:
* `ViewBinding` 类由Android Gradle Plugin在java编译器之前自动生成。如果java编译由于不相关的原因失败,这样可以避免很多真实的错误.
* 在v1中,编译应用程序时将重新生成库的绑定类(分享生成的代码并访问最终的`BR`和`R`文件).在v2中,库保留其生成的绑定类以及映射器信息,从而显着提高多模块项目的数据绑定性能。
请注意,这个新编译器向后不兼容,因此使用v1编译的库不能被v2使用,反之亦然。
V2也减少了一些很少使用的功能:
* 在v1中,一个应用程序能够提供可以覆盖依赖中的适配器的绑定适配器.在v2中,它只会对您自己的模块/应用程序及其依赖项中的代码生效.
* 以前,如果一个布局文件在2个或更多不同资源配置中包含一个具有相同id,不同类的View.Data Binding将会查找最常见的父类.在V2中,当类型和配置不匹配时,它会默认是`view`.
* 在v2中,不同的模块将不能在manifest使用相同的包名,因为Data Binding将会使用该包名来生成绑定映射器类.
#Data Binding Layout Files 数据绑定布局文件
##Writing your first set of data binding expressions 编写第一个数据绑定表达式
Data-binding 布局文件是略有不同的,以`layout`根标签开始,后面跟着`data`元素和`view`根元素.这个view元素是你在一个非绑定布局中的根布局,举个栗子:
```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
```
`data`中的user `variable`描述了一个属性,可以在这个布局中使用.
```
<variable name="user" type="com.example.User"/>
```
布局中的表达式使用@{}语法.下面是将TextView的text设置成user的firstName属性
```
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
```
##Data Object 数据对象
让我们假设有一个普通的java对象类User:
```
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
```
这种类型的对象的数据从不变化.在应用程序中读取一次,数据一直不变的数据很正常.也可以使用JavaBeans对象:
```
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
```
从数据板顶的角度看,有两个类是等价的.TextView中`android:text`属性使用的@{user.firstName}表达式将会获取前一个类的`firstName`变量和后面类中的 `getFirstName()`方法.无论哪种方式,如果该方法存在,都会被解析成`firstName()`方法.
##Binding Data 绑定数据
默认情况下,一个Binding类会以资源文件的名称为基础创建,将其转化为驼峰命名并以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);
}
```
完成后,运行程序,你可以在UI中看到Test 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 事件绑定
Data Binding允许您编写处理从view分发事件的表达式(如onClick)。事件属性名称由监听方法的名称控制,只有少数例外。例如,`View.OnLongClickListener`有一个onLongClick()方法.所以这个时间的属性是`android:onLongClick`,有两种方法去处理事件.
* Method References:在表达式中,可以引用符合listener方法签名的方法。当表达式计算为方法引用时,Data Binding将方法引用和所有者对象包装在listener中,并且将listener设置到目标view上.如果表达式计算为null,则Data Binding不会创建listener,而是设置一个空listener。
* Listener Bindings:这是在事件发生时被计算的lambda表达式.Data Binding 总是创建一个listener,并将它设置到view上.当事件被分发时,listener计算lambda表达式
###Method References 方法引用
事件可以直接绑定到处理程序方法,类似于`android:onClick`可以分配到一个Activity中.相比较`view#onClick`一个主要的优势是该表达式在编译时被处理,所以如果这个方法不存在或者签名不正确,你会收到一个编译时错误.
Method References和Listener Bindings主要的区别是实际的监听器是在数据绑定时创建的,而不是在事件触发时.如果你喜欢在事件发生时计算表达式,你应该使用listener binding.
将事件分配给它的处理程序,使用一个正常的绑定表达式,以值为方法名调用.举个例子,如果你的data对象有两个方法:
```
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
```
绑定表达式可以为view指派一个click listener:
```
<?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 Bindings 监听事件绑定
Listener Bindings 是当事件发生时才运行的绑定表达式.它和方法引用相似 ,但是它可以运行任何数据绑定的表达式.此特性适用于Gradle 2.0版及更高版的Android Gradle插件.
在方法引用中,该方法的参数必须和listener的参数匹配.在监听器绑定中,只有你的返回值必须和listener匹配(除非返回值是void).举个栗子,你可以有一个具有下面方法的presenter类:
```
public class Presenter {
public void onSaveClick(Task task){}
}
```
然后,你可以按照下面的写法绑定click事件:
```
<?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表达式代理.当表达式中使用回调时,Data Binding自动为该事件创建相关的监听器和注册器.当view触发该事件,Data Bindign解析给出的表达式.在一般的绑定表达式中,在编译监听表达式时,你任然可以获取null和线程安全性.
注意上面的例子,我们没有定义传入`onClick(android.view.View)`的`view`参数.Listener bindings 为监听参数提供了两种选择:你可以省略忽略该方法的所有参数或者命名所有参数.如果你喜欢声明参数,你可以在你的表达式中使用它.举个栗子,上面的表达式将会改为:
```
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`,你的表达式也必须返回相同类型的返回值.举个栗子,如果你想监听long click 事件,你的表达式必须返回`boolean`值.
```
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
```
```
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
```
如果表达式不能被解析成null对象.Data Binding返回该类型的默认值.举个栗子,引用类型为`null`,`int`类型为`0`,`boolen`类型是`false`,等等.
如果你需要使用带断言的表达式(如,三元运算符),你可以使用`void`作为符号.
```
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
```
####Avoid Complex Listeners 避免复杂的监听
Listener表达式非常强大,可以让你的代码非常容易阅读。另一方面,监听器包含复杂的表达式会使你的布局难以阅读和无法维护.这些表达式应该像从UI传递数据到回调方法一样简单.你应该在把业务逻辑放到监听表达式调用的回调方法中处理.
存在一些专门的单击事件,他们需要一个除了`android:onClick`来避免冲突.下面的属性就是为了避免冲突而创建的:
![image.png](https://upload-images.jianshu.io/upload_images/2595400-dfdd50b411e6bcb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#Layout Details 布局详情
##Imports 导包
`data`元素内可以使用0个或多个`import`元素.这些可以轻松引用布局文件中的类,就像在Java中一样.
```
<data>
<import type="android.view.View"/>
</data>
```
现在,你可以在你的绑定表达式中使用View了
```
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
```
当类名冲突的时候,其中一个类可以重命名为一个"别名":
```
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
```
现在,`Vista`类被用作`com.example.real.estate.View`的引用,并且`View`类作为`android.view.View`在布局文件中.导入的类型可以用作变量和表达式中的引用.
```
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
```
注意:Android Studio暂不支持自动导包.你的应用可以正常使用,你可以在你的变量定义中通过使用全包名来解决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.*`一样的自动导入的.
##Variables 变量
`data`元素内可以使用任意数量的`variable`元素.layout中的每个`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>
```
变量的类型在编译器被检查,如果一个变量实现了`android.databinding.Observable`或者是[observable collection](https://developer.android.google.cn/topic/libraries/data-binding/index.html#observable_collections),那应该被反应在类型中.如果变量是不实现Observable*接口的基类或接口,将不会被观察!
当布局文件有不同的配置(横向或纵向),变量将被合并.这些 布局文件直接必须不存在冲突的变量定义.
自动生成的绑定类对于每个描述的变量都提供了setter和getter方法.变量将具有java默认值,直到setter方法被调用-引用类型中的`null`,`int`类型的`0`,`boolean`的`false`,等等.
根据需求为我们的绑定表达式生成一个特殊的变量`context`. `context`的值是根布局的`getContext()`方法提供的Context.`context`变量奖杯具有该名称的显示变量所覆盖.
##Custom Binding Class Names 自定义绑定类的名称
默认情况下,绑定类是以布局文件的名称为基础生成的,以大写字母开头,删除下划线并将后面字母大写,最后以"Binding"结尾.这个类将会生成在模块包下的数据绑定包下.举个栗子,布局文件`contact_item.xml`将会生成`ContactItemBinding`.如果模块的包名是`com.example.my.app`,该类将会创建在`com.example.my.app.databinding`包下面.
通过调整`data`元素的`class`属性,绑定类可以重命名或放在不同包下.举个栗子:
```
<data class="ContactItem">
...
</data>
```
这将会在模块的包下面,databindingg包下生成一个`ContactItem`类.如果想要在模块包内不同的包下生成这个类,需要在前面加".":
```
<data class=".ContactItem">
...
</data>
```
在这个例子中, `ContactItem`是直接在模块包中生成的.如果你提供的是一个全包名的话,可以在任意包下生成:
```
<data class="com.example.ContactItem">
...
</data>
```
##Includes include标签
变量从包含的布局传递到include的布局的绑定中,通过程序的命名空间和属性的变量名:
```
<?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`变量.
Data Binding不支持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>
```
##Expression Language 表达式语法
###Common Features 通用特性
表达式语法非常像Java表达式.相同的有:
* 数学运算 `+ - / * %`
* 字符串拼接 `+`
* 逻辑运算 `&& ||`
* 二进制 `& | ^`
* 一元运算 `+ - ! ~`
* 位运算 `>> >>> <<`
* 比较 ` == > < >= <=`
* `instanceof`
* 分组 `()`
* 数据类型 `character,String,numeric,null`
* 类型转换
* 方法回调
* 数据属性
* 数组 `[]`
* 三元操作符 `?:`
例子:
```
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
```
###Missing Operations 缺失的语法
缺少了一小部分Java中的表达式语法.
* this
* super
* new
* 显式泛型调用
###Null Coalescing Operator 空合并运算符
空合并运算符(??)如果不是空的话就用左边的,是空的话就用右边的.
```
android:text="@{user.displayName ?? user.lastName}"
```
功能上等同于:
```
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
```
###Property Reference 属性引用
在"Writing your first set of data binding expressions"节中已经讨论过了,简单的JavaBean引用.当在一个类中,一个表达式引用一个属性时,他使用相同的格式对于字段,获取,或者观察者的字段.
```
android:text="@{user.lastName}"
```
###Avoiding NullPointerException 避免空指针
生成的数据绑定的代码hi自动检测null值,避免空指针.举个栗子,在表达式`@{user.name}`中,如果`user`的值为`null`,`user.name`将分配其默认值(null).如果你引用的是`user.age`,age的是`int`类型,默认值将会是0.
###Collections 集合
普通集合:arrays,list,sparse list,map集合,可以通过`[]`操作符方便的访问.
```
<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]}"
```
##String Literals 字符串文本
在属性值的外面使用单引号,在表达式中使用双引号就会很容易:
```
android:text='@{map["firstName"]}'
```
也可以在属性值的外面使用双引号,如果这样做了,字符串需要用正引号或反引号:
```
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
```
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)}"
```
有些资源需要明确解析类型.
![image.png](https://upload-images.jianshu.io/upload_images/2595400-35e23c0cd806890f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##Data Objects 数据对象
任何普通JavaBeans([POJO](https://baike.baidu.com/item/POJO/3311958?fr=aladdin))都可以用于数据绑定,但是修改POJO不会导致UI更新.数据绑定的真正能力是使用时,可以赋予数据对象变化时的通知能力.有三种不同的更改通知机制,Observable对象,observable字段和observable集合.
当这些可观察数据对象之一绑定到UI并且数据对象的属性改变时,UI将会自动更新.
##Observable Objects 观察者对象
一个类实现了` android.databinding.Observable`接口,将允许绑定单个监听器去绑定对象来监听对象上所有的属性改变.
android.databinding.Observable接口具有添加和删除侦听器的机制,但通知由开发人员决定。为了简化开发,一个基类,`android.databinding.BaseObservable`被创建来实现监听器注册机制.数据实现类仍然可以负责当属性改变时通知.这是通过将一个android.databinding.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);
}
}
```
android.databinding.Bindable注解是编译期间在BR类文件中生成一个条目。BR类文件将在模块包中生成。如果数据类的基类无法更改,则android.databinding.Observable接口可以使用方便的android.databinding.PropertyChangeRegistry实现,以有效地存储和通知监听器.
##ObservableFields 观察者字段
创建android.databinding.Observable类需要做一点工作,因此想要节省时间或具有少量属性的开发人员可以使用`android.databinding.ObservableField`的兄弟姐妹,
```
android.databinding.ObservableBoolean,
android.databinding.ObservableByte,
android.databinding.ObservableChar,
android.databinding.ObservableShort,
android.databinding.ObservableInt,
android.databinding.ObservableLong,
android.databinding.ObservableFloat,
android.databinding.ObservableDouble,
android.databinding.ObservableParcelable.
```
ObservableFields是具有单个字段的独立可观察对象。原始版本在访问操作期间避免装箱和取消装箱。要使用,请在数据类中创建公共最终字段:
```
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();
```
##Observable Collections 观察者集合
一些应用程序使用更多动态结构来保存数据。观察者集合允许对这些数据对象通过键访问。`android.databinding.ObservableArrayMap`是有用的当key是一种引用类型,比如String.
```
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"/>
```
当key是一个integer类型时使用android.databinding.ObservableArrayList更好:
```
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
```
在布局中,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"/>
```
#Generated Binding 生成绑定
生成的绑定类将布局变量与布局中的视图链接起来。如前所述,绑定的名称和包可能是自定义的。生成的绑定类全部集成android.databinding.ViewDataBinding。
##Creating
应该在膨胀后立即创建绑定,以确保View层次结构在绑定到布局中带有表达式的视图之前不受干扰。有几种方法可以绑定到布局。最常见的是在Binding类上使用静态方法。膨胀方法膨胀了View层次结构并将其绑定到一步。有一个更简单的版本只需要一个`LayoutInflater`,另一个需要一个`ViewGroup`:
```
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
```
如果布局使用不同的机制进行充气,则可能需要单独绑定:
```
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
```
有时绑定不能预先知道。在这种情况下,可以使用`android.databinding.DataBindingUtil`类创建绑定:
```
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,parent,attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
```
##Views With IDs
将在布局中为每个视图根据ID生成一个公开的最终字段。该绑定是视图结构上通过ID获取View的唯一的入口.这种机制可能比调用多个view的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不像没有数据绑定那样必要,但仍然有一些情况下代码仍然需要访问视图。
##Variables
每个变量将被赋予访问器方法。
```
<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>
```
将会在绑定类中生成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);
```
##ViewStubs
`ViewStubs`标签和普通的View稍有不同.他们从不可见的时候开始,当他们要么变得可见时,要么被明确告知膨胀时,他们通过膨胀另一种布局来取代布局。因为视图是最终的,所以`android.databinding.ViewStubProxy`对象取代了`ViewStub`,使开发人员能够在`ViewStub`存在时访问`ViewStub`,并且在`ViewStub`被充满时也可以访问充满的`View`层次结构。
当膨胀另一个布局时,必须为新布局建立绑定。因此,`ViewStubProxy`必须侦听`ViewStub`的`ViewStub.OnInflateListener`并在此时建立绑定。由于只有一个可以存在,因此`ViewStubProxy`允许开发人员在建立绑定后设置一个`OnInflateListener`,它将调用它。
###Advanced Binding 高级绑定
####Dynamic Variables 动态变量
有时,特定的绑定类将不被知道。例如,针对任意布局的`RecyclerView.Adapter`不会知道特定的绑定类。它必须在`onBindViewHolder(VH,int)`期间分配绑定值。
在这个例子中,RecyclerView绑定到的所有布局都有一个“item”变量。 BindingHolder有一个返回android.databinding.ViewDataBinding库的getBinding方法。
```
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
```
###Immediate Binding 即使绑定
当变量或观察者变化时,绑定将被安排在下一帧之前改变,然而,有时候,绑定必须立即执行.要强制执行,请使用`android.databinding.ViewDataBinding.executePendingBindings()`方法。
###Background Thread
只要不是集合,您可以在子线程中更改数据模型。数据绑定将在编译时实例化每个变量/字段以避免任何并发问题
#Attribute Setters
对于一个属性,数据绑定试图找到方法setAttribute。属性的命名空间并不重要,只有属性名称本身。
例如,与 `TextView` 的属性 `android:text` 相关联的表达式将查找 `setText(String)` 。如果表达式返回一个int,则数据绑定将搜索setText(int)方法。请注意让表达式返回正确的类型,如果需要的话可以强转。请注意,即使给定名称不存在任何属性,数据绑定也会起作用。然后,您可以使用数据绑定轻松地为任何setter创建属性。例如,支持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}"/>
```
##Renamed Setters 重命名Setter方法
一些属性具有不匹配名称的setter。对于这些方法,可以通过 `android.databinding.BindingMethods`注解将一个属性与setter相关联。这必须与一个类相关联,并且包含`android.databinding.BindingMethod`注解,每个重命名的方法一个注释。例如,`android:tint`属性确实与`setImageTintList(ColorStateList)`关联,而不是`setTint`。
```
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
```
开发人员不太可能需要重命名setter; android框架属性已经实现
##Custom Setters
一些属性需要自定义绑定逻辑。例如,android:paddingLeft属性没有关联的setter。但是,setPadding(左,上,右,下)存在。具有`android.databinding.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是String类型的,并且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`类有助于跟踪以前的侦听器,以便它们可以在`Binding Adaper`中删除。
通过在接口`OnViewDetachedFromWindow `和`OnViewAttachedToWindow `中添加`@TargetApi(VERSION_CODES.HONEYCOMB_MR1)`注解,数据绑定代码生成器知道只能在API11及以上版本使用,该版本支持`[addOnAttachStateChangeListener(View.OnAttachStateChangeListener)`
#Converters 转换器
##Object Conversions 对象转换
从绑定表达式返回对象时,将从自动,重命名和自定义设置器中选择一个setter。该对象将被转换为所选setter的参数类型。
这对于那些使用ObservableMaps来保存数据的人来说很方便。例如:
```
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
```
`userMap`返回一个Object,并且该Object将自动转换为在setter` setText(CharSequence)`中找到的参数类型。当可能对参数类型产生混淆时,开发人员需要在表达式中进行转换。
##Custom Conversions
有时转换应该在特定类型之间自动进行。例如,在设置背景时:
```
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
```
在这里,背景需要一个`Drawable`,但颜色是一个整数.每当需要一个`Drawable`并返回一个整数时,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"/>
```
#Android Studio Support for Data Binding
Android Studio支持许多用于数据绑定代码的代码编辑功能。例如,它支持数据绑定表达式的以下功能:
* 语法高亮显示
* 表达语言语法错误的标记
* XML代码补全
* 参考资料,包括导航(如导航到声明)和快速文档
注意:数组和一个泛型类型(如android.databinding.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中所述。
最后推荐两篇DataBinding使用的博客
[从零开始的Android新项目7 - Data Binding入门篇](http://blog.zhaiyifan.cn/2016/06/16/android-new-project-from-0-p7/)
[从零开始的Android新项目8 - Data Binding高级篇](http://blog.zhaiyifan.cn/2016/07/06/android-new-project-from-0-p8/)
https://blog.csdn.net/qiang_xi/article/category/6995820
https://blog.csdn.net/qiang_xi/article/details/75379321
#Data Binding Library 数据绑定库
本文档解释了如何使用Data Binding Library编写声明式布局,并尽量减少绑定应用程序逻辑和布局所需的胶合代码。
Data Binding Library提供了灵活性和广泛的兼容性 - 它是一个支持库,所以您可以将它与所有Android平台版本一起使用回到Android 2.1(API级别7+)。
使用DataBinding要求Gradle插件为1.5.0-alpha1及以上版本。[查看如何升级Gradle插件](https://developer.android.google.cn/studio/releases/gradle-plugin.html#updating-plugin)。
#Build Environment 构建环境
使用Data Binding之前,需要用Android SDK manager 在Support repository中下载依赖库。
配置app使用data binding,在app模块下的build.gradle文件中添加dataBinding元素。
```
android {
....
dataBinding {
enabled = true
}
}
```
如果你的app模块依赖的library使用了data binding,你的app模块也必须在它的build.gradle文件中配置data binding。
同时,确定你使用的是兼容版本的Android Studio,Android Studio 1.3及更高版本支持data binding ,如[Android Studio Support for Data Binding](https://developer.android.google.cn/topic/libraries/data-binding/index.html#studio_support)中所说的那样。
#Data Binding Compiler V2 数据绑定编译器V2
Android Gradle Plugin 3.1.0 Canary 6 附带了一个可选的新编译器。要开始使用它,更新你的gradle.properties 文件使它包含下面这行内容
```
android.databinding.enableV2=true
```
在编译器V2中:
* `ViewBinding` 类由Android Gradle Plugin在java编译器之前自动生成。如果java编译由于不相关的原因失败,这样可以避免很多真实的错误.
* 在v1中,编译应用程序时将重新生成库的绑定类(分享生成的代码并访问最终的`BR`和`R`文件).在v2中,库保留其生成的绑定类以及映射器信息,从而显着提高多模块项目的数据绑定性能。
请注意,这个新编译器向后不兼容,因此使用v1编译的库不能被v2使用,反之亦然。
V2也减少了一些很少使用的功能:
* 在v1中,一个应用程序能够提供可以覆盖依赖中的适配器的绑定适配器.在v2中,它只会对您自己的模块/应用程序及其依赖项中的代码生效.
* 以前,如果一个布局文件在2个或更多不同资源配置中包含一个具有相同id,不同类的View.Data Binding将会查找最常见的父类.在V2中,当类型和配置不匹配时,它会默认是`view`.
* 在v2中,不同的模块将不能在manifest使用相同的包名,因为Data Binding将会使用该包名来生成绑定映射器类.
#Data Binding Layout Files 数据绑定布局文件
##Writing your first set of data binding expressions 编写第一个数据绑定表达式
Data-binding 布局文件是略有不同的,以`layout`根标签开始,后面跟着`data`元素和`view`根元素.这个view元素是你在一个非绑定布局中的根布局,举个栗子:
```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
```
`data`中的user `variable`描述了一个属性,可以在这个布局中使用.
```
<variable name="user" type="com.example.User"/>
```
布局中的表达式使用@{}语法.下面是将TextView的text设置成user的firstName属性
```
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
```
##Data Object 数据对象
让我们假设有一个普通的java对象类User:
```
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
```
这种类型的对象的数据从不变化.在应用程序中读取一次,数据一直不变的数据很正常.也可以使用JavaBeans对象:
```
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
```
从数据板顶的角度看,有两个类是等价的.TextView中`android:text`属性使用的@{user.firstName}表达式将会获取前一个类的`firstName`变量和后面类中的 `getFirstName()`方法.无论哪种方式,如果该方法存在,都会被解析成`firstName()`方法.
##Binding Data 绑定数据
默认情况下,一个Binding类会以资源文件的名称为基础创建,将其转化为驼峰命名并以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);
}
```
完成后,运行程序,你可以在UI中看到Test 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 事件绑定
Data Binding允许您编写处理从view分发事件的表达式(如onClick)。事件属性名称由监听方法的名称控制,只有少数例外。例如,`View.OnLongClickListener`有一个onLongClick()方法.所以这个时间的属性是`android:onLongClick`,有两种方法去处理事件.
* Method References:在表达式中,可以引用符合listener方法签名的方法。当表达式计算为方法引用时,Data Binding将方法引用和所有者对象包装在listener中,并且将listener设置到目标view上.如果表达式计算为null,则Data Binding不会创建listener,而是设置一个空listener。
* Listener Bindings:这是在事件发生时被计算的lambda表达式.Data Binding 总是创建一个listener,并将它设置到view上.当事件被分发时,listener计算lambda表达式
###Method References 方法引用
事件可以直接绑定到处理程序方法,类似于`android:onClick`可以分配到一个Activity中.相比较`view#onClick`一个主要的优势是该表达式在编译时被处理,所以如果这个方法不存在或者签名不正确,你会收到一个编译时错误.
Method References和Listener Bindings主要的区别是实际的监听器是在数据绑定时创建的,而不是在事件触发时.如果你喜欢在事件发生时计算表达式,你应该使用listener binding.
将事件分配给它的处理程序,使用一个正常的绑定表达式,以值为方法名调用.举个例子,如果你的data对象有两个方法:
```
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
```
绑定表达式可以为view指派一个click listener:
```
<?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 Bindings 监听事件绑定
Listener Bindings 是当事件发生时才运行的绑定表达式.它和方法引用相似 ,但是它可以运行任何数据绑定的表达式.此特性适用于Gradle 2.0版及更高版的Android Gradle插件.
在方法引用中,该方法的参数必须和listener的参数匹配.在监听器绑定中,只有你的返回值必须和listener匹配(除非返回值是void).举个栗子,你可以有一个具有下面方法的presenter类:
```
public class Presenter {
public void onSaveClick(Task task){}
}
```
然后,你可以按照下面的写法绑定click事件:
```
<?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表达式代理.当表达式中使用回调时,Data Binding自动为该事件创建相关的监听器和注册器.当view触发该事件,Data Bindign解析给出的表达式.在一般的绑定表达式中,在编译监听表达式时,你任然可以获取null和线程安全性.
注意上面的例子,我们没有定义传入`onClick(android.view.View)`的`view`参数.Listener bindings 为监听参数提供了两种选择:你可以省略忽略该方法的所有参数或者命名所有参数.如果你喜欢声明参数,你可以在你的表达式中使用它.举个栗子,上面的表达式将会改为:
```
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`,你的表达式也必须返回相同类型的返回值.举个栗子,如果你想监听long click 事件,你的表达式必须返回`boolean`值.
```
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
```
```
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
```
如果表达式不能被解析成null对象.Data Binding返回该类型的默认值.举个栗子,引用类型为`null`,`int`类型为`0`,`boolen`类型是`false`,等等.
如果你需要使用带断言的表达式(如,三元运算符),你可以使用`void`作为符号.
```
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
```
####Avoid Complex Listeners 避免复杂的监听
Listener表达式非常强大,可以让你的代码非常容易阅读。另一方面,监听器包含复杂的表达式会使你的布局难以阅读和无法维护.这些表达式应该像从UI传递数据到回调方法一样简单.你应该在把业务逻辑放到监听表达式调用的回调方法中处理.
存在一些专门的单击事件,他们需要一个除了`android:onClick`来避免冲突.下面的属性就是为了避免冲突而创建的:
![image.png](https://upload-images.jianshu.io/upload_images/2595400-dfdd50b411e6bcb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#Layout Details 布局详情
##Imports 导包
`data`元素内可以使用0个或多个`import`元素.这些可以轻松引用布局文件中的类,就像在Java中一样.
```
<data>
<import type="android.view.View"/>
</data>
```
现在,你可以在你的绑定表达式中使用View了
```
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
```
当类名冲突的时候,其中一个类可以重命名为一个"别名":
```
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
```
现在,`Vista`类被用作`com.example.real.estate.View`的引用,并且`View`类作为`android.view.View`在布局文件中.导入的类型可以用作变量和表达式中的引用.
```
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
```
注意:Android Studio暂不支持自动导包.你的应用可以正常使用,你可以在你的变量定义中通过使用全包名来解决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.*`一样的自动导入的.
##Variables 变量
`data`元素内可以使用任意数量的`variable`元素.layout中的每个`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>
```
变量的类型在编译器被检查,如果一个变量实现了`android.databinding.Observable`或者是[observable collection](https://developer.android.google.cn/topic/libraries/data-binding/index.html#observable_collections),那应该被反应在类型中.如果变量是不实现Observable*接口的基类或接口,将不会被观察!
当布局文件有不同的配置(横向或纵向),变量将被合并.这些 布局文件直接必须不存在冲突的变量定义.
自动生成的绑定类对于每个描述的变量都提供了setter和getter方法.变量将具有java默认值,直到setter方法被调用-引用类型中的`null`,`int`类型的`0`,`boolean`的`false`,等等.
根据需求为我们的绑定表达式生成一个特殊的变量`context`. `context`的值是根布局的`getContext()`方法提供的Context.`context`变量奖杯具有该名称的显示变量所覆盖.
##Custom Binding Class Names 自定义绑定类的名称
默认情况下,绑定类是以布局文件的名称为基础生成的,以大写字母开头,删除下划线并将后面字母大写,最后以"Binding"结尾.这个类将会生成在模块包下的数据绑定包下.举个栗子,布局文件`contact_item.xml`将会生成`ContactItemBinding`.如果模块的包名是`com.example.my.app`,该类将会创建在`com.example.my.app.databinding`包下面.
通过调整`data`元素的`class`属性,绑定类可以重命名或放在不同包下.举个栗子:
```
<data class="ContactItem">
...
</data>
```
这将会在模块的包下面,databindingg包下生成一个`ContactItem`类.如果想要在模块包内不同的包下生成这个类,需要在前面加".":
```
<data class=".ContactItem">
...
</data>
```
在这个例子中, `ContactItem`是直接在模块包中生成的.如果你提供的是一个全包名的话,可以在任意包下生成:
```
<data class="com.example.ContactItem">
...
</data>
```
##Includes include标签
变量从包含的布局传递到include的布局的绑定中,通过程序的命名空间和属性的变量名:
```
<?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`变量.
Data Binding不支持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>
```
##Expression Language 表达式语法
###Common Features 通用特性
表达式语法非常像Java表达式.相同的有:
* 数学运算 `+ - / * %`
* 字符串拼接 `+`
* 逻辑运算 `&& ||`
* 二进制 `& | ^`
* 一元运算 `+ - ! ~`
* 位运算 `>> >>> <<`
* 比较 ` == > < >= <=`
* `instanceof`
* 分组 `()`
* 数据类型 `character,String,numeric,null`
* 类型转换
* 方法回调
* 数据属性
* 数组 `[]`
* 三元操作符 `?:`
例子:
```
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
```
###Missing Operations 缺失的语法
缺少了一小部分Java中的表达式语法.
* this
* super
* new
* 显式泛型调用
###Null Coalescing Operator 空合并运算符
空合并运算符(??)如果不是空的话就用左边的,是空的话就用右边的.
```
android:text="@{user.displayName ?? user.lastName}"
```
功能上等同于:
```
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
```
###Property Reference 属性引用
在"Writing your first set of data binding expressions"节中已经讨论过了,简单的JavaBean引用.当在一个类中,一个表达式引用一个属性时,他使用相同的格式对于字段,获取,或者观察者的字段.
```
android:text="@{user.lastName}"
```
###Avoiding NullPointerException 避免空指针
生成的数据绑定的代码hi自动检测null值,避免空指针.举个栗子,在表达式`@{user.name}`中,如果`user`的值为`null`,`user.name`将分配其默认值(null).如果你引用的是`user.age`,age的是`int`类型,默认值将会是0.
###Collections 集合
普通集合:arrays,list,sparse list,map集合,可以通过`[]`操作符方便的访问.
```
<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]}"
```
##String Literals 字符串文本
在属性值的外面使用单引号,在表达式中使用双引号就会很容易:
```
android:text='@{map["firstName"]}'
```
也可以在属性值的外面使用双引号,如果这样做了,字符串需要用正引号或反引号:
```
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
```
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)}"
```
有些资源需要明确解析类型.
![image.png](https://upload-images.jianshu.io/upload_images/2595400-35e23c0cd806890f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##Data Objects 数据对象
任何普通JavaBeans([POJO](https://baike.baidu.com/item/POJO/3311958?fr=aladdin))都可以用于数据绑定,但是修改POJO不会导致UI更新.数据绑定的真正能力是使用时,可以赋予数据对象变化时的通知能力.有三种不同的更改通知机制,Observable对象,observable字段和observable集合.
当这些可观察数据对象之一绑定到UI并且数据对象的属性改变时,UI将会自动更新.
##Observable Objects 观察者对象
一个类实现了` android.databinding.Observable`接口,将允许绑定单个监听器去绑定对象来监听对象上所有的属性改变.
android.databinding.Observable接口具有添加和删除侦听器的机制,但通知由开发人员决定。为了简化开发,一个基类,`android.databinding.BaseObservable`被创建来实现监听器注册机制.数据实现类仍然可以负责当属性改变时通知.这是通过将一个android.databinding.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);
}
}
```
android.databinding.Bindable注解是编译期间在BR类文件中生成一个条目。BR类文件将在模块包中生成。如果数据类的基类无法更改,则android.databinding.Observable接口可以使用方便的android.databinding.PropertyChangeRegistry实现,以有效地存储和通知监听器.
##ObservableFields 观察者字段
创建android.databinding.Observable类需要做一点工作,因此想要节省时间或具有少量属性的开发人员可以使用`android.databinding.ObservableField`的兄弟姐妹,
```
android.databinding.ObservableBoolean,
android.databinding.ObservableByte,
android.databinding.ObservableChar,
android.databinding.ObservableShort,
android.databinding.ObservableInt,
android.databinding.ObservableLong,
android.databinding.ObservableFloat,
android.databinding.ObservableDouble,
android.databinding.ObservableParcelable.
```
ObservableFields是具有单个字段的独立可观察对象。原始版本在访问操作期间避免装箱和取消装箱。要使用,请在数据类中创建公共最终字段:
```
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();
```
##Observable Collections 观察者集合
一些应用程序使用更多动态结构来保存数据。观察者集合允许对这些数据对象通过键访问。`android.databinding.ObservableArrayMap`是有用的当key是一种引用类型,比如String.
```
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"/>
```
当key是一个integer类型时使用android.databinding.ObservableArrayList更好:
```
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
```
在布局中,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"/>
```
#Generated Binding 生成绑定
生成的绑定类将布局变量与布局中的视图链接起来。如前所述,绑定的名称和包可能是自定义的。生成的绑定类全部集成android.databinding.ViewDataBinding。
##Creating
应该在膨胀后立即创建绑定,以确保View层次结构在绑定到布局中带有表达式的视图之前不受干扰。有几种方法可以绑定到布局。最常见的是在Binding类上使用静态方法。膨胀方法膨胀了View层次结构并将其绑定到一步。有一个更简单的版本只需要一个`LayoutInflater`,另一个需要一个`ViewGroup`:
```
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
```
如果布局使用不同的机制进行充气,则可能需要单独绑定:
```
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
```
有时绑定不能预先知道。在这种情况下,可以使用`android.databinding.DataBindingUtil`类创建绑定:
```
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,parent,attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
```
##Views With IDs
将在布局中为每个视图根据ID生成一个公开的最终字段。该绑定是视图结构上通过ID获取View的唯一的入口.这种机制可能比调用多个view的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不像没有数据绑定那样必要,但仍然有一些情况下代码仍然需要访问视图。
##Variables
每个变量将被赋予访问器方法。
```
<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>
```
将会在绑定类中生成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);
```
##ViewStubs
`ViewStubs`标签和普通的View稍有不同.他们从不可见的时候开始,当他们要么变得可见时,要么被明确告知膨胀时,他们通过膨胀另一种布局来取代布局。因为视图是最终的,所以`android.databinding.ViewStubProxy`对象取代了`ViewStub`,使开发人员能够在`ViewStub`存在时访问`ViewStub`,并且在`ViewStub`被充满时也可以访问充满的`View`层次结构。
当膨胀另一个布局时,必须为新布局建立绑定。因此,`ViewStubProxy`必须侦听`ViewStub`的`ViewStub.OnInflateListener`并在此时建立绑定。由于只有一个可以存在,因此`ViewStubProxy`允许开发人员在建立绑定后设置一个`OnInflateListener`,它将调用它。
###Advanced Binding 高级绑定
####Dynamic Variables 动态变量
有时,特定的绑定类将不被知道。例如,针对任意布局的`RecyclerView.Adapter`不会知道特定的绑定类。它必须在`onBindViewHolder(VH,int)`期间分配绑定值。
在这个例子中,RecyclerView绑定到的所有布局都有一个“item”变量。 BindingHolder有一个返回android.databinding.ViewDataBinding库的getBinding方法。
```
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
```
###Immediate Binding 即使绑定
当变量或观察者变化时,绑定将被安排在下一帧之前改变,然而,有时候,绑定必须立即执行.要强制执行,请使用`android.databinding.ViewDataBinding.executePendingBindings()`方法。
###Background Thread
只要不是集合,您可以在子线程中更改数据模型。数据绑定将在编译时实例化每个变量/字段以避免任何并发问题
#Attribute Setters
对于一个属性,数据绑定试图找到方法setAttribute。属性的命名空间并不重要,只有属性名称本身。
例如,与 `TextView` 的属性 `android:text` 相关联的表达式将查找 `setText(String)` 。如果表达式返回一个int,则数据绑定将搜索setText(int)方法。请注意让表达式返回正确的类型,如果需要的话可以强转。请注意,即使给定名称不存在任何属性,数据绑定也会起作用。然后,您可以使用数据绑定轻松地为任何setter创建属性。例如,支持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}"/>
```
##Renamed Setters 重命名Setter方法
一些属性具有不匹配名称的setter。对于这些方法,可以通过 `android.databinding.BindingMethods`注解将一个属性与setter相关联。这必须与一个类相关联,并且包含`android.databinding.BindingMethod`注解,每个重命名的方法一个注释。例如,`android:tint`属性确实与`setImageTintList(ColorStateList)`关联,而不是`setTint`。
```
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
```
开发人员不太可能需要重命名setter; android框架属性已经实现
##Custom Setters
一些属性需要自定义绑定逻辑。例如,android:paddingLeft属性没有关联的setter。但是,setPadding(左,上,右,下)存在。具有`android.databinding.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是String类型的,并且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`类有助于跟踪以前的侦听器,以便它们可以在`Binding Adaper`中删除。
通过在接口`OnViewDetachedFromWindow `和`OnViewAttachedToWindow `中添加`@TargetApi(VERSION_CODES.HONEYCOMB_MR1)`注解,数据绑定代码生成器知道只能在API11及以上版本使用,该版本支持`[addOnAttachStateChangeListener(View.OnAttachStateChangeListener)`
#Converters 转换器
##Object Conversions 对象转换
从绑定表达式返回对象时,将从自动,重命名和自定义设置器中选择一个setter。该对象将被转换为所选setter的参数类型。
这对于那些使用ObservableMaps来保存数据的人来说很方便。例如:
```
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
```
`userMap`返回一个Object,并且该Object将自动转换为在setter` setText(CharSequence)`中找到的参数类型。当可能对参数类型产生混淆时,开发人员需要在表达式中进行转换。
##Custom Conversions
有时转换应该在特定类型之间自动进行。例如,在设置背景时:
```
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
```
在这里,背景需要一个`Drawable`,但颜色是一个整数.每当需要一个`Drawable`并返回一个整数时,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"/>
```
#Android Studio Support for Data Binding
Android Studio支持许多用于数据绑定代码的代码编辑功能。例如,它支持数据绑定表达式的以下功能:
* 语法高亮显示
* 表达语言语法错误的标记
* XML代码补全
* 参考资料,包括导航(如导航到声明)和快速文档
注意:数组和一个泛型类型(如android.databinding.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中所述。
最后推荐两篇DataBinding使用的博客
[从零开始的Android新项目7 - Data Binding入门篇](http://blog.zhaiyifan.cn/2016/06/16/android-new-project-from-0-p7/)
[从零开始的Android新项目8 - Data Binding高级篇](http://blog.zhaiyifan.cn/2016/07/06/android-new-project-from-0-p8/)
https://blog.csdn.net/qiang_xi/article/category/6995820
https://blog.csdn.net/qiang_xi/article/details/75379321