Data Binding (中文文档)

Data Binding 类库

这篇文档将教你如何运用 Data Binding 类库来编写声明试布局,而且尽可能减小粘合代码对你的应用逻辑和布局上的绑定。 Data Binding 是一种灵活和普遍兼容的类库,它是一个支持库,所以你能够在任何 Android 2.1(API level 7+) 以上的设备 使用。 为了使用 Data Binding,Android Gradle 插件版本必须为 1.5.0-alpha1 或以上,查看 如何升级你的 Gradle 插件html

构建环境

为了获取 Data Binding,去 Android SDK manager 下载 它的支持库。 在你的应用 module 的 build.gradle 添加 dataBinding 来让你的应用支持 Data Binding。 用如下代码片断来配置 Data Binding:java

android {
    ....
    dataBinding {
        enabled = true
    }
}
复制代码

若你有一个应用 module 用了一个依赖了 Data Binding 的类库,也同样要在该 module 中配置开启 Data Binding。 另外,若是想使用 Data Binding,大家你的 Android Studio 版本必须等于或大于 1.3。android


Data Binding 布局文件

编写你的第一个 Data Binding 表达式

Data Binding 的布局文件有一点不同,它以 layout 标签做为根标签,而且有一个data 元素和 一个 view 元素做为子标签,这个 view 元素就是你没有使用 Data Binding 时该有的布局文件。如下是一个例子:api

<?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 标签下的 variable 是你在这个 Data Binding 布局文件中有可能使用到的对象。数组

<variable name="user" type="com.example.User"/>
复制代码

布局中使用 @{} 语法来包裹 variable 中的对象属性,在下面例子中,TextViewtext 属性的值用 userfirstName 属性来替代。安全

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>
复制代码

数据对象

如今让咱们假设你有一个普通的 Java 对象(POJO)Userbash

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}
复制代码

这个对象属性(final 修饰)是不可变的,若是你的数据对象只提供只读权限而且以后不会再去修改的话,这种作法很广泛。咱们也能够用 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;
   }
}
复制代码

从数据绑定的角度来看,这两个类是等价的。TextViewandroid:text 属性值会经过表达式 @{user.firstName} 来获取第一个类中的 fistName 字段值,活着获取第二个类中的 getFirstName() 方法返回的值。另外,若是 firstName() 方法存在的话也是能够获取到值的。app

绑定数据

默认状况下,将根据布局文件的名称生成一个绑定类,将其转换为 Pascal 格式并将 Binding 做为其后缀。上面的布局文件是名称 main_activity.xml ,所以生成的绑定类是 MainActivityBinding。这个类将布局属性(例如用户变量)绑定到布局的视图中,并知道如何经过表达式来赋值。建立绑定类的最简单方式是在视图 inflate 的时候:框架

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}
复制代码

完成了!运行这个应用,你会在界面中看到测试的 User。另外,你能够经过一些方 式获取绑定类:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
复制代码

若是你在 ListView 或者 RecyclerView 中使用数据绑定电话,你能够经过一些方式获取绑定类:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
复制代码

事件处理

数据绑定容许你编写表达式来处理从视图中分派的事件(例如 onClick)。除少数例外,事件属性名称由侦听器中的方法名称来肯定。例如,View.OnLongClickListener 有一个 onLongClick()方法,因此这个事件的属性是 android:onLongClick。有如下两种方式来处理一个事件。

  • 方法引用:在表达式中,能够引用符合侦听器方法签名的方法。 当表达式被评估为方法引用时,数据绑定将方法引用和全部者对象包装在侦听器中,并将该侦听器设置在目标视图上。 若是表达式被评估为 null,则数据绑定不会建立侦听器,而是设置一个空的侦听器。
  • 监听器绑定:当事件发生时,lambda 表达式将被评估。 数据绑定老是会在视图上建立一个监听器。 当事件被发送时,监听器将评估 lambda 表达式。

方法引用

事件能够直接绑定处处理的方法中,相似于 android:onClick 能够做为 Activity 的一个方法同样。与 View#onClick 属性相比,一个主要的优势是表达式在编译时被处理,所以若是方法不存在或者它的签名不正确,就会收到编译时错误。

方法引用和监听器绑定的主要区别在于实际的监听器实现是在绑定数据时建立的,而不是在事件触发时建立的。

要将事件分配给其处理程序,请使用常规绑定表达式,其值是要调用的方法名称。 例如,若是你的数据对象有两个方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}
复制代码

绑定表达式能够为 View 分配一个点击监听器:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>
复制代码

请注意,表达式中方法的签名必须与监听器对象中方法的签名彻底匹配。

监听器绑定

监听器绑定是事件发生时运行的绑定表达式。相似于方法引用,可是容许你运行任意的数据绑定表达式。 此功能适用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。

在方法引用中,方法的参数必须与事件侦听器的参数匹配。 在监听器绑定中,只有你的返回值必须与监听器的指望返回值相匹配(除非它返回值为 void )。 例如,您能够有一个具备如下方法的 Presenter 类:

public class Presenter {
    public void onSaveClick(Task task){}
}
复制代码

而后你能够绑定你的点击事件到你的类中,例如:

<?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>
复制代码

监听器仅能够容许用 lambda 表达式做为根元素。 当表达式中有回调时,数据绑定会自动为事件建立必要的侦听器和注册表。 当视图触发事件时,数据绑定将评估给定的表达式。 就像在常规的绑定表达式同样,当这些监听器表达式被评估的时候,你仍然能够获取数据绑定的空值和保证线程安全。

请注意,在上面的例子中,咱们没有定义传入 onClick(android.view.View) 的视图参数。 监听器绑定为监听器参数提供了两个选择:您能够忽略该方法的全部参数或将其所有命名。 若是您想要命名参数,则能够在表达式中使用它们。 例如,上面的表达式能够写成:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"
复制代码

或者若是你想使用表达式中的参数,能够像下面这样:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
复制代码
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
复制代码

你能够在 lambda 表达式中使用多个参数:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
复制代码
<CheckBox 
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
复制代码

若是正在侦听的事件返回值不是 void,则表达式必须返回相同类型的值。 例如,若是你想监听长按事件,你的表达式应该返回布尔值。

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
复制代码
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
复制代码

若是因为空对象而没法评估表达式,Data Binding 将返回该类型的默认 Java 值。 例如,引用类型为 nullint0booleanfalse 等等。

若是您须要使用带谓词的表达式(例如三元),则可使用 void 做为符号。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
复制代码

避免复杂的监听器

监听器表达式很是强大,可让你的代码变得很是容易阅读。 另外一方面,包含复杂表达式的监听器也会使您的布局难以阅读和维护。这些表达式应该像从 UI 中传递可用数据到回调方法同样简单。你应该从侦听器表达式调用的回调方法内实现业务逻辑。 存在一些专门的单击事件处理程序,它须要除 android:onClick 以外的其余属性以免冲突。 已建立了如下属性以免这种冲突:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

布局文件细节

Imports

数据元素内可使用零个或多个 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 引入。 导入的类型能够用做变量和表达式中的类型引用:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>
复制代码

注意:Android Studio 还没有处理导入,所以自动导入变量在你的的 IDE 中可能没法完成。 你的应用程序仍然能够正常编译,你能够经过在变量定义中使用彻底限定的名称来解决 IDE 的这个问题。

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

当在表达式中引用静态字段和方法时,也可使用导入的类型:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

就像在 Java 文件中同样,java.lang.* 会被自动导入。

Variables

data 元素内可使用任意的 variable。 每一个变量表示能够在布局中设置的属性,以用于布局文件中的绑定表达式。

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

变量类型在编译时被检查,因此若是一个变量实现了 Observable 或者一个 observable collection,那么它应该被反映在类型中。 若是变量是没有实 Observable 接口的基类或接口,那么它将不会被观察!

当不一样的配置(例如横向或纵向)有不一样的布局文件时,变量将被合并。 这些布局文件之间不得有冲突的变量定义。

生成的绑定类将为每一个描述的变量设置一个 settergetter 方法。 变量将采用默认的 Java 值,直到调用 setter 为止 。对于引用类型为 null,对于 int0,对于 booleanfalse 等。

自定义绑定类的名字

默认状况下,根据布局文件的名称生成一个绑定类,以大写字母开头,删除下划线(_)并以后的单词首字母大写,而后添加后缀 Binding。 这个类将被放置在模块包下的数据绑定包中。 例如,布局文件 contact_item.xml 将生成 ContactItemBinding。 若是模块包是 com.example.my.app,那么它将被放置在 com.example.my.app.databinding 中。

绑定类能够经过调整 data 元素的 class 属性来重命名或放置在不一样的包中。 例如:

<data class="ContactItem">
    ...
</data>
复制代码

这会在模块包中的数据绑定包中生成绑定类 ContactItem。 若是该类应该在模块包中的其余包中生成,则能够用“.”做为前缀:

<data class=".ContactItem">
    ...
</data>
复制代码

在这种状况下,直接在模块包中生成了 ContactItem。 若是提供完整的包,则可使用任意的包:

<data class="com.example.ContactItem">
    ...
</data>
复制代码

Includes

经过在属性中使用应用程序命名空间和变量名称,变量能够从包含的布局中传递到包含的布局的绑定中:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>
复制代码

在这里,name.xmlcontact.xml 布局文件中都必须有一个 user 变量。

数据绑定不支持 include 做为 merge 元素的直接子元素。 例如,不支持如下布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>
复制代码

表达式语言

共同特征

表达式语言看起来很像 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 ?: 例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
复制代码

缺乏的操做

你在 Java 中使用的一些表达式语法并不支持绑定操做。

  • this
  • super
  • new
  • 明确的泛型调用

空的合并运算符

空合并运算符 ?? 会选择左边的运算结果(若是它不是 null 的话)或右边的运算结果(若是它是 null 的话)。

android:text="@{user.displayName ?? user.lastName}"
复制代码

这在功能上等同于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
复制代码

属性引用

当一个表达式引用一个类的属性时,它对字段,setterObservableFields 使用相同的格式。

android:text="@{user.lastName}"
复制代码

避免空指针异常

生成的数据绑定代码会自动检查空值并避免空指针异常。 例如,在表达式 @ {user.name} 中,若是 user 为 null,则 user.name 将被分配其默认值(null)。 若是引用 user.age,其中age是一个 int,那么它将默认为0。

集合

通用的集合:数组,列表,SparseArray ,map,可使用 [] 运算符来方便地访问。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
复制代码

字符串文本

在属性值两边使用单引号时,则表达式中使用双引号:

android:text='@{map["firstName"]}'
复制代码

也可使用双引号来包围属性值。 这样作时,字符串文字应该使用单引号 ' 或者反引号(`)。

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
复制代码

资源

使用正常的语法能够将资源做为表达式的一部分进行访问:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
复制代码

格式字符串和复数能够经过提供参数来评估:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
复制代码

当一个复数有多个参数时,全部参数都应该传递:

Have an orange
 Have %d oranges
 android:text="@{@plurals/orange(orangeCount, orangeCount)}"
复制代码

一些资源须要明确的类型评估:

Type Normal Reference Expression Reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Data Objects

任何普通的旧 Java 对象(POJO)均可以用于数据绑定,但修改 POJO 不会致使 UI 更新。 数据绑定的真正威力在于经过给你的数据对象在数据改变时提供通知。 有三种不一样的数据更改通知机制,Observable objects, observable fields, observable collections.

当这些可观察的数据对象被绑定到 UI,而且数据对象的属性改变时,UI 将被自动更新。

Observable Objects

实现 Observable 接口的类将容许绑定单个侦听器附加到绑定对象,以侦听该对象上全部属性的更改。

Observable 接口具备添加和删除侦听器的功能,但通知是由开发者决定的。 为了简化开发,建立了基类 BaseObservable,以实现侦听器注册机制。 数据类实现者仍然负责通知属性的更改。 这是经过给 getter 分配一个 Bindable 注解并通知 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 来实现,以有效地存储和通知监听器。

ObservableFields

建立 Observable 类须要作一点工做,因此想要节省时间或拥有不多属性的开发人员可使用 ObservableField 及其同胞 ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoublObservableParcelableObservableFields 是具备单个字段的独立的可观察对象。 原始版本在访问操做期间避免装箱和取消装箱。 要使用,请在数据类中建立一个公共 final 字段:

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}
复制代码

就是这样!要访问该值,请使用 set 和 get 方法访问:

user.firstName.set("Google");
int age = user.age.get();
复制代码

Observable Collections

一些应用程序使用更多的动态结构来保存数据,观察集合容许对这些数据对象进行键值访问。当键是引用类型(如 String)时,ObservableArrayMap 很是有用。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
复制代码

在布局文件中,map 经过字符串键来访问:

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

当键是整形是,可使用 ObservableArrayList:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
复制代码

在布局中,列表能够经过索引来访问:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</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"/>
复制代码

生成绑定类

生成的绑定类将布局变量与布局中的视图连接起来。 如前所述,绑定的名称和包多是自定义的。 生成的绑定类都扩展了 ViewDataBinding

建立

应该在 inflate 以后当即建立绑定,以确保 View 层次结构不受干扰。 有几种方法能够绑定到布局。 最多见的是在绑定类中使用静态方法。inflate 方法 inflate View 层次结构,一步到位。 有一个更简单的版本,只须要一个 LayoutInflater 和一个 ViewGroup

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
复制代码

若是布局使用不一样的机制 inflate,它可能会被分开绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
复制代码

有时绑定不能预先知道。 在这种状况下,可使用 DataBindingUtil 类建立绑定:

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

Views With IDs

将在布局中为每一个视图生成一个公开的 final 字段。 该绑定在视图层次结构上执行单个传递,提取带有 ID 的视图。 这个机制能够比调用多个视图的 findViewById 更快。 例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>
复制代码

会生成带有一下字段的绑定类:

public final TextView firstName;
public final TextView lastName;
复制代码

IDs 不像没有数据绑定那样必要,可是仍然有一些状况下代码须要访问视图。

变量

每一个变量将被赋予访问器方法。

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

会在绑定类中生成 setter 和 getter 方法:

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

ViewStub 与普通视图有点不一样。 他们从不可见的时候开始,当他们要么变得可见时,要么被明确告知 inflate 时,他们经过 inflate 另外一个布局来取代布局。

因为 ViewStub 本质上从视图层次中消失,因此绑定对象中的视图也必须消失以容许收集。 由于视图是 final 的,因此 ViewStubProxy 对象代替了ViewStub,当 ViewStub 存在时,开发人员能够访问 ViewStub,而且在 ViewStub被 inflate 时也能够访问被 inflate 的视图。

当 inflate 另外一个布局时,必须为新的布局创建绑定。所以,ViewStubProxy 必须侦听 ViewStubViewStub.OnInflateListener 并在此时创建绑定。因为只有一个能够存在,ViewStubProxy 容许开发者在创建绑定以后设置一个 OnInflateListener 对象。

高级绑定

动态变量

有时,特定的绑定类将不被知道。 例如,针对任意布局的 RecyclerView.Adapter 将不知道具体的绑定类。 它仍然必须在 onBindViewHolder(VH,int) 期间分配绑定值。

在这个例子中,RecyclerView 绑定的全部布局都有一个 item 变量。BindingHolder 有一个返回 ViewDataBinding 基类的 getBinding 方法。

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

当即绑定

当变量或 observable 变化时,绑定将被安排在下一帧以前改变。但有时候,绑定必须当即执行。要强制执行,请使用 executePendingBindings() 方法。

后台线程

只要不是集合,就能够在后台线程中更改数据模型。数据绑定将在评估时本地化每一个变量/字段,以免任何并发问题。


属性设置

每当绑定值发生变化时,生成的绑定类必须使用绑定表达式在视图上调用setter方法。 数据绑定框架能够自定义调用哪一个方法来设置值。

自动的设置器

对于一个属性,数据绑定将试图找到设置属性的方法。属性的命名空间并不重要,只有属性名称自己才重要。例如,与 TextView 的属性 android:text 相关联的表达式将查找 setText(String)。 若是表达式返回 int,那么数据绑定将搜索一个 setText(int) 方法。请注意让表达式返回正确的类型,若是须要的话就进行转换。即便给定名称不存在任何属性,数据绑定也能够工做。 而后,您可使用数据绑定轻松地为任何 setter 建立属性。 例如,support 库中的 DrawerLayout 没有任何属性,可是有不少 setter。 您可使用自动设置器来使用其中的一个。

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

重命名设置器

一些属性的设置器会与名称不匹配。 对于这些方法,一个属性可能经过 BindingMethods 注解与设置器关联。 这必须与一个类相关联,每一个重命名的方法一个包含一个 BindingMethod 注解。例如,android:tint 属性确实与 setImageTintList(ColorStateList) 关联,而不是 setTint

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

开发人员不太可能须要重命名设置器, 安卓框架已经为这些属性实现了。

自定义设置器

一些属性须要自定义绑定逻辑。 例如,android:paddingLeft 属性没有关联的设置器。 相反,setPadding(eft, top, right, bottom) 存在。 使用 BindingAdapter 注释的静态绑定适配器方法容许开发人员自定义如何调用属性的设置器。

安卓属性已经建立了 BindingAdapters。 例如,这里是 paddingLeft

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

绑定适配器对其余类型的自定义很是有用。 例如,一个自定义的加载器能够被调用脱机线程来加载一个图像。当发生冲突时,开发人员建立的绑定适配器将覆盖数据绑定默认适配器。您也可让适配器接收多个参数。

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
复制代码
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
复制代码

若是 imageUrl 和 error 都用于 ImageView 且 imageUrl 是字符串,而且 error 是 drawable,则将调用此适配器。 自定义名称空间在匹配过程当中被忽略。 也能够为 android 命名空间编写适配器。 绑定适配器方法能够选择在其处理程序中使用旧值。 采用新旧值的方法,应该把属性的全部旧的值放在第一位,而后是新的值:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}
复制代码

事件处理器只能用于只有一个抽象方法的接口或抽象类。例如:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}
复制代码

当一个监听器有多个方法时,它必须被分红多个监听器。例如,View.OnAttachStateChangeListener 有两个方法:onViewAttachedToWindow()onViewDetachedFromWindow()。而后咱们必须建立两个接口来区分它们的属性和处理器。

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

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

由于更改一个侦听器也会影响另外一个侦听器,因此咱们必须有三个不一样的绑定适配器,一个用于每一个属性,另外一个用于两个,它们都应该被设置。

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}
复制代码

上面的例子比正常状况稍微复杂一点,由于视图对侦听器使添加和删除,而不是对 View.OnAttachStateChangeListener 使用set方法。 android.databinding.adapters.ListenerUtil 类有助于跟踪之前的监听器,以便它们能够在绑定适配器中被移除。经过使用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 注解接口 OnViewDetachedFromWindowOnViewAttachedToWindow,数据绑定代码生成器知道只应在 API 12 或以上的设备上调用 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 来运行运行侦听器。


转换器

对象转换

从绑定表达式返回一个对象时,将从自动,重命名和自定义的设置器中选择一个设置器。 该对象将被转换为所选设置器的参数类型。 这对于那些使用 ObservableMaps 来保存数据的开发者来讲是很方便的。例如:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

userMap 返回一个对象,该对象将被自动转换为在 setText(CharSequence) 中找到的参数类型。 当参数类型可能混淆时,开发者须要在表达式中输入。

自定义转换

有时转换应该在特定类型之间自动进行。 例如,设置 background 时:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

在这里,背景须要一个 Drawable,可是颜色是一个整数。每当一个 Drawable 被判断该返回一个整数时,该整形应该被转换成一个 ColorDrawable。 这个转换是经过一个带有 BindingConversion 注解的静态方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}
复制代码

请注意,转换只发生在设置器级别,因此不容许混合类型,以下所示:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

Android Studio 支持数据绑定

Android Studio 支持数据绑定代码的许多代码编辑功能。例如,它支持数据绑定表达式的如下功能:

  • 语法高亮
  • 表达式语法错误的提示
  • XML代码完成
  • 包括导航(如导航到声明)和快速文档的参考

注意:若是没有错误,则数组和通常类型(如 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 中所述。


原文地址:developer.android.google.cn/topic/libra…

相关文章
相关标签/搜索