databinding guide 翻译

Data Binding Guide

Data Binding即数据绑定,Data Binding 库实现在布局文件中实现数据绑定申明,使数据的变化引起视图的自动更新,减少了逻辑代码,在Android中可以很方便的实现MVVM的开发模式。

最低运行环境: Android 2.1 (API level 7+) 环境要求:Android Plugin for Gradle 1.3.0-beta4或者更高

Beta release

当前是beta版本,可能会有一些bug,也有可能现在不适合在你的项目中使用。Google希望大家使用它,并给出一个好的或者不好的反馈。另外需要注意的是,这个beta版跟后面的正式版使用方式上可能会有比较大的变化,在实际项目中慎用。

配置环境Setting Up Work Environment:

在project最上层的build.gradle android标签下面加入:

1
dependencies {
    classpath "com.android.tools.build:gradle:1.3.0-beta4"
    classpath "com.android.databinding:dataBinder:1.0-rc1"
}

同时确保jcenter在版本仓库列表中

1
allprojects {
   repositories {
       jcenter()
   }
}

在所有需要使用到Data Binding的module中,都需要在build.gradle的android plugin下面增加:

1
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

data binding插件的作用是增加必须的编译配置依赖到项目中。

Data Binding布局文件

Data-binding布局文件稍微有些不同,在根标签下面有一个data标签和root view的标签并行。下面是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
< ?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"></variable>
</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>

<textview android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"></textview>

</linearlayout>
</layout>

user 是会在这个布局中用到的一个变量。

1
<variable name="user" type="com.example.User"></variable>

在布局中使用的使用要使用“@{}”,如下代码表示:TextView中文本设置为user的firstName

1
2
3
<textview android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"></textview>

数据对象Data Object

假设我们有个User的POJO:

1
2
3
4
5
6
7
8
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

这个对象的数据是一直不变的。很多应用中一个数据可能变化一次以后就不再变了。有时候我们也使用下面的JavaBean结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的角度来看,这两个类是一样的。对于@{user.firstName} ,在给TextView的android:text赋值的时候,系统可以像第一个类中那样直接读取firstName的值,或者像第二个类中调用getFirstName() ,当然,如果firstName() 存在的话,也是会被调用到的。(至于同时存在的时候,调用优先顺序请自己测试。)

绑定数据Binding Data

默认情况下,会有一个基于layout文件名字产生一个Binding类,然后在后面加个Binding. 上面的main_activity.xml 产生的类名字就是MainActivityBinding 这个类持有了layout中所有的绑定属性(比如user变量)。最简单的做法是在view填充的时候创建绑定bindings.

1
2
3
4
5
6
7
@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的数据了。

下面是另一种写法:

1
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

如果你在ListView或者RecyclerView 的Adaptper中使用data binding.使用如下代码:

1
2
3
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

绑定事件Binding Events

事件可以简单的绑定到handler方法中,就像android:onClick被分配到Activity的onClick(View v)中一样。

比如我们Handler中有下面两个方法:

1
2
3
4
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}

在view中分配点击监听的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
< ?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"></variable>
<variable name="user" type="com.example.User"></variable>
</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="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"></textview>

<textview android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"></textview>

</linearlayout>
</layout>

布局相关详细Layout Details

导入Imports

如果在data元素中药使用其他的类,代码和java差不多:

1
2
3
<data>
<import type="android.view.View"></import>
</data>

现在可以在下面代码中是用View类的功能了。

1
2
3
4
<textview android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"></textview>

如果出现类名重复的情况,可以指定alias:

1
2
3
<import type="android.view.View"></import>
<import type="com.example.real.estate.View"
alias="Vista"></import>

下面是变量定义写法:

1
2
3
4
5
6
<data>
<import type="com.example.User"></import>
<import type="java.util.List"></import>
<variable name="user" type="User"></variable>
<variable name="userList" type="List<User>"/>
</variable></data>

注意:现在 Android Studio还没有在IDE实现layout文件中自动补全import。 类型转换什么的也是阔以的。

1
2
3
<textview android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></textview>

导入类后,可以使用其中的静态方法:

1
2
3
4
5
6
7
8
<data>
<import type="com.example.MyStringUtils"></import>
<variable name="user" type="com.example.User"></variable>
</data>

<textview android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></textview>

和java一样,java.lang.*下面的包是自动导入的。

Variables

1
2
3
4
5
6
<data>
<import type="android.graphics.drawable.Drawable"></import>
<variable name="user" type="com.example.User"></variable>
<variable name="image" type="Drawable"></variable>
<variable name="note" type="String"></variable>
</data>

变量类型是在编译是检查的。如果一个类实现了Observable ,那么变量值变化后UI会跟着变化的。

如果同一个同一布局有多个适配文件,文件中的变量最终会合并到一起,所以这些文件中不能存在同名的变量。

生成的绑定类binding class针对每个变量都有getter/setter方法,再调用setter方法前,这些变量都会有默认值,引用类型为null,int为0…

自定义绑定类名字Custom Binding Class Names

默认情况下,绑定类名字是布局文件名字去掉“ _ ”,首字母大写,最后加上Binding。这个文件会被放在modul包下面的package包中,比如contact_item.xml 生成ContactItemBinding 如果module名字是com.example.my.app 这个ContactItemBinding.java 会被放在 com.example.my.app.databinding下面。

绑定类可以被重命名或者放在其他package下面,调整方式如下:

1
2
3
<data class="ContactItem">
...
</data>

上面的文件还是会生成在databinding 下面。如果想生成在其他package下面,和manifest类似,以”.”开头:

1
2
3
<data class=".ContactItem">
...
</data>

当然也可以像下面一样:

1
2
3
<data class="com.example.ContactItem">
...
</data>

嵌套Includes

在嵌套的布局中使用绑定类,需要使用bind命名空间xmlns:bind="http://schemas.android.com/apk/res-auto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
< ?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"></variable>
</data>
<linearlayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<include layout="@layout/name"
bind:user="@{user}"></include>

<include layout="@layout/contact"
bind:user="@{user}"></include>

</linearlayout>
</layout>

注意:name.xmlcontact.xml 中必须要有user变量。

下面的 merge 这个是不支持的。

1
2
3
4
5
6
7
8
9
10
11
12
13
< ?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"></variable>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"></include>

<include layout="@layout/contact"
bind:user="@{user}"></include>

</merge>
</layout>

表达式语法Expression Language

共有特性Common Features

下面的用法和java一致

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:

1
2
3
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age &lt; 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

不支持的操作Missing Operations

this super new Explicit generic invocation

Null Coalescing Operator

“??”的用法是左边不为空取左边,否则取右边。

1
android:text="@{user.displayName ?? user.lastName}"

等同于:

1
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

防止NPE Avoiding NullPointerException

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. 系统已经针对null做了处理,如果user为null,@{user.name} 默认为null。user.age(int类型),会默认为0

集合操作Collections

方便起见,如下集合烈性: arrays, lists, sparse lists, and maps, 都可以通过 [] 进行操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<data>
<import type="android.util.SparseArray"></import>
<import type="java.util.Map"></import>
<import type="java.util.List"></import>
<variable name="list" type="List&lt;String>"/>
</variable><variable name="sparse" type="SparseArray&lt;String>"/>
</variable><variable name="map" type="Map&lt;String, String>"/>
</variable><variable name="index" type="int"></variable>
<variable name="key" type="String"></variable>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"

字符串字面值String Literals

为了避免双引号(“)转义的问题,可以配合单引号(‘)一起使用

1
android:text='@{map["firstName"]}'

下面的写法也ok

1
2
android:text="@{map[`firstName`}"
android:text="@{map[&quot;firstName&quot;]}"

资源文件相关Resources

根据屏幕尺寸加载不同的padding值

1
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式化字符串:

1
2
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

单复数提示语句:

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

有些资源需要详细的类型,转换方式如下:
| Type | Normal Reference | Expression Reference |
| —————– |:—————- |:——————– |
| String[] | @array | @stringArray |
| int[] | @array | @intArray |
| TypedArray | @array | @typedArray |
| Animator | @animator | @animator |
| StateListAnimator | @animator | @stateListAnimator |
| color int | @color | @color |
| ColorStateList | @color | @colorStateList |

数据对象Data Objects

pojo对象可以通过数据绑定来展示到ui,但是修改pojo对象不会使UI刷新。数据绑定真正强大的地方是数据变化的时候UI可以自动刷新。有以下三种机制可以实现:Observable objects, observable fields, observable collections.

当与ui绑定上面的对象数据发生变化时,UI会自动刷新。

Observable Objects

实现Observable 接口的类可以依附attach一个listner到绑定的对象上面,监听绑定对象数据变化。 Observable 有添加和移除监听的机制,但是什么通知是有开发者来调用的。为了让开发更简单,推荐使用BaseObservable,这个类多了个数据变化后发出通知,这个主要是通过Bindable注解在getter/setter方法上面实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 注解编译时会在modul目录生成BR 类文件。

ObservableFields

创建Observable 可能有点费时间,如果想省时一些,可以考虑ObservableField 或者其子类: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable. 在数据类中创建变量:

private static class User {
   public final ObservableField<string> firstName =
       new ObservableField<>();
   public final ObservableField</string><string> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

使用:

user.firstName.set("Google");
int age = user.age.get();

Observable Collections

对于复杂一些的结构,可以参考下面的写法 如果key是String类型:

ObservableArrayMap</string><string , Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

In the layout, the map may be accessed through the String keys:

<data>
    <import type="android.databinding.ObservableMap"></import>
    <variable name="user" type="ObservableMap&lt;String, Object>"/>
</variable></data>
…
<textview android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>
<textview android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>

如果key是integer,建议用ObservableArrayList

ObservableArrayList<object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

In the layout, the list may be accessed through the indices:

<data>
    <import type="android.databinding.ObservableList"></import>
    <import type="com.example.my.app.Fields"></import>
    <variable name="user" type="ObservableList&lt;Object>"/>
</variable></data>
…
<textview android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>
<textview android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>

###Binding生成Generated Binding

####创建
Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater还有一个是采用ViewGroup:

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

如果使用不同的机制载入layout,他可以分开绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil类来创建Binding:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

####带ID的Views

在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById还要快。例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"></variable>
   </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>
       <textview android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"></textview>
   </linearlayout>
</layout>

它会生成如下的Binding类:

public final TextView firstName;
public final TextView lastName;

IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。

####变量Variables

每个Variable会有访问方法。

<data>
    <import type="android.graphics.drawable.Drawable"></import>
    <variable name="user"  type="com.example.User"></variable>
    <variable name="image" type="Drawable"></variable>
    <variable name="note"  type="String"></variable>
</data>

它会在Binding中生成setters和getters:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

####ViewStubs

ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。

由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。

当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy必需监听ViewStub的OnInflateListener监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy允许开发者在其上设置一个OnInflateListener它会在建立Binding后调用。

####Binding进阶

#####动态Variables
有时,不知道具体的Binding类,例如,一个RecyclerView适配器对layouts任意操作并不知道具体的Binding类。它仍然必需在onBindViewHolder(VH, int)期间赋值给Binding。

在这个例子中,该RecyclerView绑定的所有layouts有一个“item”的Variable。该BindingHolder有一个getBinding方法返回ViewDataBinding。

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

#####直接BindingImmediate Binding

当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()方法。

#####后台线程

只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。

###属性Setters
每当绑定值的变化,生成的Binding类必须调用setter方法​​。Data Binding框架有可以自定义赋值的方法。

####自动Setters

对于一个属性,Data Binding试图找到setAttribute方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。

例如,有关TextView的android:text属性的表达式会寻找一个setText(String)的方法。如果表达式返回一个int,Data Binding会搜索的setText(int)方法。注意:要表达式返回正确的类型,如果需要的话使用casting。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。

<android .support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"></android>

####重命名的SettersRenamed Setters

一些有setters的属性按名称并不匹配。对于这些方法,属性可以通过BindingMethods注解相关联。这必须与一个包含BindingMethod注解的类相关联,每一个用于一个重命名的方法。例如,android:tint属性与setImageTintList相关联,而不与setTint相关。

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

以上例子,开发者需要重命名setters是不太可能了,android架构属性已经实现了。

自定义Setters

有些属性需要自定义绑定逻辑。例如,对于android:paddingLeft属性并没有相关setter。相反,setPadding(left, top, right, bottom)是存在在。一个带有BindingAdapter注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。

Android的属性已经创造了BindingAdapters。举例来说,对于paddingLeft:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

Binding适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。

当有冲突时,开发人员创建的Binding适配器将覆盖Data Binding默认适配器。

您也可以创建可以接收多个参数的适配器。

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}

<imageview app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”></imageview>

如果对于一个ImageViewimageUrl和error都被使用并且imageUrl是一个string类型以及error是一个drawable时,该适配器会被调用。

  • 匹配的过程中自定义namespaces将被忽略。
  • 你也可以为Android namespaces写适配器。

事件处理器只能用在只有一个抽象方法的接口或者抽象类中。比如:

@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);
        }
    }
}

当一个listener有多个方法的时候,它必须被分隔成多个listener。比如View.OnAttachStateChangeListener 有两个方法:onViewAttachedToWindow()onViewDetachedFromWindow() 。我们必须创建两个带有不同属性和handler处理器的接口

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

由于修改一个listener会影响到其他的,我们必须定义三个不懂的binding adpater.一个是所有方法的,两个分开的。

@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);
        }
    }
}

###转换

####对象转换

当从Binding表达式返回一个对象,一个setter会从自动、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。

这是为了方便那些使用ObservableMaps来保存数据。例如:

<textview android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>

在userMap返回一个对象并且该对象将自动转换为setText(CharSequence)的参数类型。当有关参数类型可能混乱时,开发人员需要在表达式中转换。

####自定义转换

有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:

<view android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></view>

这里,背景需要Drawable对象,但颜色是一个整数。不管何时有Drawable并且返回值是一个整数,那么整数类型会被转换为ColorDrawable。这个转换是通过使用带有BindingConversion注解的静态方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

注意:转换仅仅发生在setter级别,因此它是不允许以下混合类型

<view android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></view>

文章目录
  1. 1. Data Binding Guide
  2. 2. Beta release
  3. 3. 配置环境Setting Up Work Environment:
    1. 3.1. Data Binding布局文件
    2. 3.2. 数据对象Data Object
    3. 3.3. 绑定数据Binding Data
    4. 3.4. 绑定事件Binding Events
    5. 3.5. 布局相关详细Layout Details
      1. 3.5.1. 导入Imports
      2. 3.5.2. Variables
      3. 3.5.3. 自定义绑定类名字Custom Binding Class Names
      4. 3.5.4. 嵌套Includes
    6. 3.6. 表达式语法Expression Language
      1. 3.6.1. 共有特性Common Features
      2. 3.6.2. 不支持的操作Missing Operations
      3. 3.6.3. Null Coalescing Operator
      4. 3.6.4. 防止NPE Avoiding NullPointerException
      5. 3.6.5. 集合操作Collections
      6. 3.6.6. 字符串字面值String Literals
      7. 3.6.7. 资源文件相关Resources
  4. 4. 数据对象Data Objects
    1. 4.1. Observable Objects
      1. 4.1.1. ObservableFields
    2. 4.2. Observable Collections