Android DataBinding 详解

前几天小试牛刀写了一篇 Android DataBinding 初探,只是简单的介绍了一下 DataBinding 的几个小问题,并没有特别详细的去介绍 DataBinding 的更多方法,这几天看了一下 DataBinding 的官网的相关内容,觉得有必要把官网的用法记录一下,用来参考及以后使用时的参考,以前大家很多人都使用过注解框架,包括 Jake Wharton 大神的 ButterKnife,但 DataBinding 出来后,相信会对此类的框架形成碾压,毕竟是 Google 的官方出品,接下来我们切入正题了,开始详细的去介绍这个 Android DataBinding Library,先上一张概况图:


一、构建环境(Build Environment)

  • 要使用 DataBinding 数据库,先从 Android SDK Manager 的支持库里下载该库
  • 配置你的应用程序使用数据绑定,在应用程序模块,你的 build.gradle 文件添加数据绑定元素
  • 另外,需要注意你使用的 Android Studio 的兼容版本,需要 1.3 及以上的版本
使用下面代码段来配置数据绑定:
[java]  view plain  copy
  1. android {  
  2.     ....  
  3.     dataBinding {  
  4.         enabled = true  
  5.     }  
  6. }  
二、数据绑定布局文件(Data Binding Layout Files)

1) DataBinding 表达式

数据绑定的布局文件和我们以前经常写的布局文件稍有不同,并从布局的根标记开始,后面依次是数据元素和视图根元素,即根布局是 layout,接下来是 data 节点,variable 节点,示例如下:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  3.    <data>  
  4.        <variable name="user" type="com.example.User"/>  
  5.    </data>  
  6.    <LinearLayout  
  7.        android:orientation="vertical"  
  8.        android:layout_width="match_parent"  
  9.        android:layout_height="match_parent">  
  10.        <TextView android:layout_width="wrap_content"  
  11.            android:layout_height="wrap_content"  
  12.            android:text="@{user.firstName}"/>  
  13.        <TextView android:layout_width="wrap_content"  
  14.            android:layout_height="wrap_content"  
  15.            android:text="@{user.lastName}"/>  
  16.    </LinearLayout>  
  17. </layout>  
数据中的用户变量描述此布局中可能使用的属性:
[html]  view plain  copy
  1. <variable name="user" type="com.example.User"/>  
布局中的表达式使用 "@{}" 语法在属性中写入,在这里,TextView 的文本设置为用户 FirstName 属性:
[html]  view plain  copy
  1. <span style="color:#666666;"><TextView android:layout_width="wrap_content"  
  2.           android:layout_height="wrap_content"  
  3.           android:text="@{user.firstName}"/></span>  
2)数据对象(Data Object)

假设你现在有一个普通的 Java 对象(User):

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

这种类型的对象具有从不改变的数据,应用程序中国通常有一次读取数据,此后不会更改,也可以使用 JavaBeans 对象:

[java]  view plain  copy
  1. public class User {  
  2.    private final String firstName;  
  3.    private final String lastName;  
  4.    public User(String firstName, String lastName) {  
  5.        this.firstName = firstName;  
  6.        this.lastName = lastName;  
  7.    }  
  8.    public String getFirstName() {  
  9.        return this.firstName;  
  10.    }  
  11.    public String getLastName() {  
  12.        return this.lastName;  
  13.    }  
  14. }  
从数据绑定的角度来看,这两个类是等价的,用于 TextView 中的 android:text 属性的表达式 @{user.firstName} 将访问前者 User 对象中的 firstName 和后者 JavaBeans 对象中的 getFirstName 方法

3)数据绑定(Binding Data)

默认情况下,绑定类将根据 layout 文件的名称生成,首字母大写的命名规范,并添加 "Binding" 后缀,上述的布局文件是main_activity.xml,所以生成类是 MainActivityBinding, 该类将布局属性(例如 User 变量)的所有绑定保存到布局视图中,并知道如何为绑定表达式赋值,创建最简单的方法是在 inflating 时绑定,如下:

[java]  view plain  copy
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.    super.onCreate(savedInstanceState);  
  4.    MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);  
  5.    User user = new User("Test""User");  
  6.    binding.setUser(user);  
  7. }  
就这样,运行应用程序,你将会在 UI 中看到 Test User,或者你可以通过如下获取 View:
[java]  view plain  copy
  1. MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());  
如果你是在 ListView 或者 RecyclerView adapter 中使用 Data Binding 时,你可能使用:
[java]  view plain  copy
  1. ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);  
  2. //or  
  3. ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);  
4)事件处理(Event Handing)

数据绑定允许你编写表达式处理,如 onClick 事件,有少数例外事件属性的名称受监听方法名称的约束,例如View.OnLongClickListener 有一个 onLongClick 方法,所以此事件的属性是:Android:onLongClick,它有两种处理事件的方法:

  • 方法引用:在表达式中,可以引用符合监听方法签名的方法,当表达式计算为方法引用时,数据绑定将监听器中的方法引用和所有者对象封装在一起,并在目标视图上设置监听器,如果表达式计算为 null,则数据绑定不会创建监听器,而是设置空监听器
  • 监听器绑定:这些是在事件发生时计算 Lambda 表达式,数据绑定总是创建一个监听器,它设置在视图上,当事件被发送时,监听器计算 Lambda 表达式

 方法引用:事件可以直接绑定到处理程序的方法,类似于 Android:onClick,相比来看 View#onClick 属性更重要的优势是,表达式在编译的时候处理的,所以如果方法不存在或签名是不正确的,你将会在编译时出错,方法引用和监听器绑定之间的主要区别是,当数据绑定时,实际的监听器实现将创建,而不是在触发时,如果你希望事件发生时对表达式进行审核,则应该使用监听器绑定,若将事件分配给其处理程序,请使用常规绑定表达式,该值是要调用的方法名,例如,如果你的数据对象有两种方法:

[java]  view plain  copy
  1. public class MyHandlers {  
  2.     public void onClickFriend(View view) { ... }  
  3. }  
绑定表达式可以为视图分配单击监听器:

注意表达式中的方法的签名必须与监听对象中的方法的签名完全匹配

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  3.    <data>  
  4.        <variable name="handlers" type="com.example.Handlers"/>  
  5.        <variable name="user" type="com.example.User"/>  
  6.    </data>  
  7.    <LinearLayout  
  8.        android:orientation="vertical"  
  9.        android:layout_width="match_parent"  
  10.        android:layout_height="match_parent">  
  11.        <TextView android:layout_width="wrap_content"  
  12.            android:layout_height="wrap_content"  
  13.            android:text="@{user.firstName}"  
  14.            android:onClick="@{handlers::onClickFriend}"/>  
  15.    </LinearLayout>  
  16. </layout>  
监听器绑定:监听器绑定是在事件发生运行时的绑定表达式,它类似于方法引用,但它容许你运行任意的数据绑定表达式,需要注意的是此功能在 Android Gradle2.0 及以上版本可用,在方法引用中,该方法的参数必须与事件监听器的参数相匹配,在监听器绑定中,只有返回值必须与监听器的期望返回值相匹配,示例如下:
[java]  view plain  copy
  1. public class Presenter {  
  2.     public void onSaveClick(Task task){}  
  3. }  
然后你可以在你的 xml 文件中将点击事件绑定如下:
[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <data>  
  4.         <variable name="task" type="com.android.example.Task" />  
  5.         <variable name="presenter" type="com.android.example.Presenter" />  
  6.     </data>  
  7.     <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">  
  8.         <Button android:layout_width="wrap_content" android:layout_height="wrap_content"  
  9.         android:onClick="@{() -> presenter.onSaveClick(task)}" />  
  10.     </LinearLayout>  
  11. </layout>  

监听器由 Lambda 表达式表示,这些表达式只允许作为表达式的根元素,当表达中使用回调时,数据绑定会自动创建时间的必要监听器和寄存器,当视图触发事件时,数据绑定将审核给定的表达式,与常规绑定方式一样,在审核这些监听器表达式时,任然可以得到数据绑定的 null 和线程安全性

请注意,在上面的例子中,我们还没有定义 View 的参数,通过 onClick(Android.view.View)Listener 绑定为监听器参数提供两种选择:你可以忽略所有方法参数或命名所有参数,如果你更喜欢命名参数,则也可以在表达式中使用它们,例如,上面的表达式可以写成:

[html]  view plain  copy
  1. android:onClick="@{(view) -> presenter.onSaveClick(task)}"  
或者如果你想使用表达式中的参数,它可以写成如下:
[java]  view plain  copy
  1. public class Presenter {  
  2.     public void onSaveClick(View view, Task task){}  
  3. }  
[html]  view plain  copy
  1. android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"  
可以使用多个参数的 Lambda 表达式:

[java]  view plain  copy
  1. public class Presenter {  
  2.     public void onCompletedChanged(Task task, boolean completed){}  
  3. }  
[html]  view plain  copy
  1. <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"  
  2.         android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />  
如果监听的事件返回类型无效的值,则表达式必须返回相同类型的值。例如,如果要监听长点击事件,则表达式应返回布尔值:
[java]  view plain  copy
  1. public class Presenter {  
  2.     public boolean onLongClick(View view, Task task){}  
  3. }  
[html]  view plain  copy
  1. android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"  
你还可以使用三元表达式:
[html]  view plain  copy
  1. android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"  
避免复杂的监听

监听器表达式是非常强大的,可以使你的代码很容易阅读。另一方面,含有复杂的表达式的监听让你的布局难以阅读和维护。这些表达式应该是简单的,从用户界面传递可用数据到回调方法。您可以在从监听器表达式调用的回调方法中实现任何业务逻辑


三、布局细节(Layout Details)

1)imports

可以在数据元素内使用零个或多个导入元素。这些可以参考在你的布局文件的类,就像在 java:

[html]  view plain  copy
  1. <data>  
  2.     <import type="android.view.View"/>  
  3. </data>  

现在,视图可以在绑定表达式中使用:

[html]  view plain  copy
  1. <TextView  
  2.    android:text="@{user.lastName}"  
  3.    android:layout_width="wrap_content"  
  4.    android:layout_height="wrap_content"  
  5.    android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>  
当有类名冲突时,其中一个类可以重命名为“alias:”,如下:
[html]  view plain  copy
  1. <import type="android.view.View"/>  
  2. <import type="com.example.real.estate.View"  
  3.         alias="Vista"/>  
现在,Vista 可以用来参考的 com.example.real.estate.view 和视图可以用来参考 android.view.view 在布局文件中,导入类型可以用作变量和表达式中的类型引用:
[html]  view plain  copy
  1. <data>  
  2.     <import type="com.example.User"/>  
  3.     <import type="java.util.List"/>  
  4.     <variable name="user" type="User"/>  
  5.     <variable name="userList" type="List<User>"/>  
  6. </data>  
注意:Android Studio 还不能很好的兼容支持,变量可能不能在 IDE 中完成自动提示功能。但是你的应用程序将仍然可以编译,你可以通过使用完全限定名称来定义变量解决 IDE 的问题:
[html]  view plain  copy
  1. <TextView  
  2.    android:text="@{((User)(user.connection)).lastName}"  
  3.    android:layout_width="wrap_content"  
  4.    android:layout_height="wrap_content"/>  
在表达式中引用静态字段和方法时也可以使用导入类型:
[html]  view plain  copy
  1. <data>  
  2.     <import type="com.example.MyStringUtils"/>  
  3.     <variable name="user" type="com.example.User"/>  
  4. </data>  
  5. …  
  6. <TextView  
  7.    android:text="@{MyStringUtils.capitalize(user.lastName)}"  
  8.    android:layout_width="wrap_content"  
  9.    android:layout_height="wrap_content"/>  
就像在 java 语言,java * 自动导入

2)Variables

可以在数据元素内使用任意数量的变量元素。每个变量元素描述可以在布局文件中用于绑定表达式中的布局的属性:

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

在编译时检查变量类型,因此,如果变量实现 Observable 或 Observable 的集合,则该类型应反映在类型中。如果变量是不执行 Observable* 接口的基类或接口,则不会观察变量,当有不同的布局文件的各种配置(如头像),变量将被合并。这些布局文件之间不能有冲突的变量定义生成一个名为上下文的特殊变量,用于在需要时绑定表达式。上下文的值是根目录的 getcontext(),上下文变量将被一个显式变量声明所覆盖

3)自定义绑定类名(Custom Binding Class Names)

默认情况下,绑定类是基于布局的文件名生成,开始用大写,去掉下划线 "_",然后加后缀 "Binding",这个类将会被放置在一个绑定包的模块包下,例如,布局文件是 contant_item.xml 将生成 ContactItemBinding,如果模块封装为 com.example.my.app,那么它将被放置在 com.example.my.app.databinding

通过调整数据元素的类属性可以将绑定类重命名或放置在不同的包中。例如:

[html]  view plain  copy
  1. <data class="ContactItem">  
  2.     ...  
  3. </data>  
这个生成绑定类中的模块封装在数据绑定包 ContactItem,如果类产生在不同的包中的模块封装内,它可能会加 ".",如下:
[html]  view plain  copy
  1. <data class=".ContactItem">  
  2.     ...  
  3. </data>  
在这种情况下,ContactItem 直接在模块封装生成,如果提供完整的包,任何包可以使用:
[html]  view plain  copy
  1. <data class="com.example.ContactItem">  
  2.     ...  
  3. </data>  
4)Includes

通过使用应用程序命名空间和属性中的变量名,可以将变量从包含布局中传递到包含布局中:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         xmlns:bind="http://schemas.android.com/apk/res-auto">  
  4.    <data>  
  5.        <variable name="user" type="com.example.User"/>  
  6.    </data>  
  7.    <LinearLayout  
  8.        android:orientation="vertical"  
  9.        android:layout_width="match_parent"  
  10.        android:layout_height="match_parent">  
  11.        <include layout="@layout/name"  
  12.            bind:user="@{user}"/>  
  13.        <include layout="@layout/contact"  
  14.            bind:user="@{user}"/>  
  15.    </LinearLayout>  
  16. </layout>  
在这里,必须有两 name.xml 和 contact.xml 布局文件的用户变量

数据绑定不支持包括合并元素的直接子项。例如,不支持下列布局:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         xmlns:bind="http://schemas.android.com/apk/res-auto">  
  4.    <data>  
  5.        <variable name="user" type="com.example.User"/>  
  6.    </data>  
  7.    <merge>  
  8.        <include layout="@layout/name"  
  9.            bind:user="@{user}"/>  
  10.        <include layout="@layout/contact"  
  11.            bind:user="@{user}"/>  
  12.    </merge>  
  13. </layout>  
5)表达式(Expression Language)

常用表达式给 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(三元运算) "?:"
示例如下:
[html]  view plain  copy
  1. android:text="@{String.valueOf(index + 1)}"  
  2. android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"  
  3. android:transitionName='@{"image_" + id}'  
缺少的操作:
  • this
  • super
  • new
  • Explicit generic invocation
null 合并运算符:
null 合并运算符(??)左边不为 null 或正确,则返回左边,否则返回右边,如下:
[html]  view plain  copy
  1. android:text="@{user.displayName ?? user.lastName}"  
在功能上和如下相同:
[html]  view plain  copy
  1. android:text="@{user.displayName != null ? user.displayName : user.lastName}"  

属性引用:

第一个前边已经提到了,DataBinding 表达式:JavaBean 引用的简短格式
当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters 以及 ObservableFields
[html]  view plain  copy
  1. android:text="@{user.lastName}"  
避免空指针(Avoiding NullPointerException)
Data Binding 代码生成时会自动检查是否为 null,来避免出现 null pointer exception 错误,例如在表达式 @{user.name} 中,如果 user 是 null,user.name 会赋予它的默认值 null,如果你引用 @{user.age},age 是 int 类型,那么它的默认值是 0

集合:

常见的集合:数组,列表,稀疏列表和 maps,可以访问使用 [ ] 运算符
[html]  view plain  copy
  1. <data>  
  2.     <import type="android.util.SparseArray"/>  
  3.     <import type="java.util.Map"/>  
  4.     <import type="java.util.List"/>  
  5.     <variable name="list" type="List<String>"/>  
  6.     <variable name="sparse" type="SparseArray<String>"/>  
  7.     <variable name="map" type="Map<String, String>"/>  
  8.     <variable name="index" type="int"/>  
  9.     <variable name="key" type="String"/>  
  10. </data>  
  11. …  
  12. android:text="@{list[index]}"  
  13. …  
  14. android:text="@{sparse[index]}"  
  15. …  
  16. android:text="@{map[key]}"  
字符串常量(String Literals):

当在属性值周围使用单引号时,表达式中使用双引号:
[html]  view plain  copy
  1. android:text='@{map["firstName"]}'  

使用双引号来包含属性值也可以,字符串前需要使用"`":
[html]  view plain  copy
  1. android:text="@{map[`firstName`}"  
  2. android:text="@{map['firstName']}"  

资源(Resources):

可以使用正常语法的表达式访问资源:
[html]  view plain  copy
  1. android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"  

格式字符串和复数可通过提供参数判断:
[html]  view plain  copy
  1. android:text="@{@string/nameFormat(firstName, lastName)}"  
  2. android:text="@{@plurals/banana(bananaCount)}"  

当复数需要多个参数时,所有参数都应通过:
[html]  view plain  copy
  1.   Have an orange  
  2.   Have %d oranges  
  3.   
  4. android:text="@{@plurals/orange(orangeCount, orangeCount)}"  

一些资源需要显示类型判断:


四、数据对象(Data Objects)

任何普通的 java 对象(POJO)可用于数据绑定,但修改一个 POJO 不会造成UI更新。数据绑定的真正能力可以通过给数据对象在数据变化时通知来使用,有三种不同的数据更改通知机制,Observable 对象,ObservableFilelds 字段和 Observable Cllections 集合,当这些可观察数据对象绑定到 UI 和数据对象的属性更改时,用户界面将自动更新

Observable 对象(Observable Objects)
实现 Observable 接口类允许单个监听器连接到绑定对象以监听该对象所有属性的改变

Observable 接口有一个机制来添加和删除监听器,但通知是由开发人员管理,为使开发更容易,一个 BaseObservable 基类是为了实现监听器注册机制而创建。Data 类实现者仍然是负责通知时的性能变化。这是通过分配一个 Bindable 注释 getter 和 setter 进行通知来完成
[java]  view plain  copy
  1. private static class User extends BaseObservable {  
  2.    private String firstName;  
  3.    private String lastName;  
  4.    @Bindable  
  5.    public String getFirstName() {  
  6.        return this.firstName;  
  7.    }  
  8.    @Bindable  
  9.    public String getLastName() {  
  10.        return this.lastName;  
  11.    }  
  12.    public void setFirstName(String firstName) {  
  13.        this.firstName = firstName;  
  14.        notifyPropertyChanged(BR.firstName);  
  15.    }  
  16.    public void setLastName(String lastName) {  
  17.        this.lastName = lastName;  
  18.        notifyPropertyChanged(BR.lastName);  
  19.    }  
  20. }  

Bindable 在编译过程中生成绑定在 BR 类的文件条目,BR 类文件会在模块包内生成,如果 Data 类的基类不能改变,Observable 接口通过 PropertyChangeRegistry 可以方便的来实现用于存储和有效的通知监听器

Observable 字段(ObservableFields)

一个小工作会涉及到创建 Observable 类,所以开发者们想节省时间或仅有几个特性可以用 observablefield 和它的同类observableboolean,observablebyte,observablechar,observableshort,observableint,observablelong,observablefloat,observabledouble,和observableparcelable。observablefields 完备可观察对象有一个单一的领域。原始版本避免装箱和拆箱过程中访问操作。若要使用,请在数据类中创建 public final 字段:
[java]  view plain  copy
  1. private static class User {  
  2.    public final ObservableField<String> firstName =  
  3.        new ObservableField<>();  
  4.    public final ObservableField<String> lastName =  
  5.        new ObservableField<>();  
  6.    public final ObservableInt age = new ObservableInt();  
  7. }  
是这么回事!访问值,使用设置和获取方法:
[java]  view plain  copy
  1. user.firstName.set("Google");  
  2. int age = user.age.get();  

Observable 集合(Observable Clollections)

一些应用程序使用更多的动态结构来保存数据。Observable 集合允许对这些数据对象进行键控访问。ObservableArrayMap 用于键是引用类型,关键是引用类型,比如 String:
[java]  view plain  copy
  1. ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();  
  2. user.put("firstName""Google");  
  3. user.put("lastName""Inc.");  
  4. user.put("age"17);  
在布局中,map 可以通过字符串键访问:
[html]  view plain  copy
  1. <data>  
  2.     <import type="android.databinding.ObservableMap"/>  
  3.     <variable name="user" type="ObservableMap<String, Object>"/>  
  4. </data>  
  5. …  
  6. <TextView  
  7.    android:text='@{user["lastName"]}'  
  8.    android:layout_width="wrap_content"  
  9.    android:layout_height="wrap_content"/>  
  10. <TextView  
  11.    android:text='@{String.valueOf(1 + (Integer)user["age"])}'  
  12.    android:layout_width="wrap_content"  
  13.    android:layout_height="wrap_content"/>  
ObservableArrayList 用于键是整数:
[java]  view plain  copy
  1. ObservableArrayList<Object> user = new ObservableArrayList<>();  
  2. user.add("Google");  
  3. user.add("Inc.");  
  4. user.add(17);  
在布局中,可以通过索引访问 List:
[html]  view plain  copy
  1. <data>  
  2.     <import type="android.databinding.ObservableList"/>  
  3.     <import type="com.example.my.app.Fields"/>  
  4.     <variable name="user" type="ObservableList<Object>"/>  
  5. </data>  
  6. …  
  7. <TextView  
  8.    android:text='@{user[Fields.LAST_NAME]}'  
  9.    android:layout_width="wrap_content"  
  10.    android:layout_height="wrap_content"/>  
  11. <TextView  
  12.    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'  
  13.    android:layout_width="wrap_content"  
  14.    android:layout_height="wrap_content"/>  

五、Binding 生成(Generated Binding)

生成的 Banging 类将布局 variables 与布局内的 views 连接起来。如前所述,Binding 的名称和包可以自定义。生成的 Binding 类都扩展 ViewDataBinding

创建(Creating)

Binding 应在 inflation 后不久创建,以确保 View 层次结构在绑定到 Views 中的表达式之前不会受到干扰。有几个方法绑定到layout,最常见的是使用静态方法 Binding 类。inflation 方法载入 View 的层次结构并且绑定到它只需要这一步,结合这一步,还有一个更简单的版本,只需要一个 LayoutInflater,或一个 ViewGroup:
[java]  view plain  copy
  1. MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);  
  2. MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);  
如果 layout 使用不同的机制 inflate,则可以单独绑定:
[java]  view plain  copy
  1. MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);  

有时 Binding 不能事先知道,在这种情况下,可以使用 DataBindingUtil 类来创建 Binding:
[java]  view plain  copy
  1. ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,  
  2.     parent, attachToParent);  
  3. ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);  

ID 绑定(Views With IDs)

将在 layout 中使用 ID 生成每个 View 的公共 public final 字段,Binding 在 View 层次结构上执行单个传递,提取带 ID 的Views。这种机制可以比调用 findViewById 更快。例如:
[html]  view plain  copy
  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.    <data>  
  3.        <variable name="user" type="com.example.User"/>  
  4.    </data>  
  5.    <LinearLayout  
  6.        android:orientation="vertical"  
  7.        android:layout_width="match_parent"  
  8.        android:layout_height="match_parent">  
  9.        <TextView android:layout_width="wrap_content"  
  10.            android:layout_height="wrap_content"  
  11.            android:text="@{user.firstName}"  
  12.    android:id="@+id/firstName"/>  
  13.        <TextView android:layout_width="wrap_content"  
  14.            android:layout_height="wrap_content"  
  15.            android:text="@{user.lastName}"  
  16.   android:id="@+id/lastName"/>  
  17.    </LinearLayout>  
  18. </layout>  
将生成一个 Binding 类:
[java]  view plain  copy
  1. public final TextView firstName;  
  2. public final TextView lastName;  

IDS 几乎没有必要在 DataBinding,但仍然有一些情况下,访问 Views 仍然是必要的代码

Variables:

每个 vaiables 都会有给定的访问方法:
[html]  view plain  copy
  1. <data>  
  2.     <import type="android.graphics.drawable.Drawable"/>  
  3.     <variable name="user"  type="com.example.User"/>  
  4.     <variable name="image" type="Drawable"/>  
  5.     <variable name="note"  type="String"/>  
  6. </data>  
它会在Binding中结合生成setters和getters:
[java]  view plain  copy
  1. public abstract com.example.User getUser();  
  2. public abstract void setUser(com.example.User user);  
  3. public abstract Drawable getImage();  
  4. public abstract void setImage(Drawable image);  
  5. public abstract String getNote();  
  6. public abstract void setNote(String note);  

ViewStubs:

ViewStubs 相比正常的 Views 略有不同,它们开始时是不可见的,当它们设置为可见或告知要载入时,它们通过载入另外一个layout 取代自己
由于 ViewStub 基本消失从View的层次结构,

因为 viewstub 基本消失从视图层次,在 Binding 对象的 View 也必须消失让收集,因为Views是最后一个 ViewStubProxy 对象以取代 ViewStub 的地方,给开发者获得 ViewStub,当 ViewStub 已经被载入时,它还可以存在并访问载入的 View 层结构

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

高级绑定(Advanced Binding)

动态变量:
有时,不知道具体的 Binding 类,例如,一个 RecyclerView 适配器对任意 layout 并不知道具体的 Binding 结合类,它任然必需在 onBindViewHolder 期间赋值给 Binding
在下面的例子中,该 RecyclerView 绑定的所有 layouts 有一个 "item"的Variable,该 BindingHolder 有一个 getBinding 方法返回 ViewDataBinding:
[java]  view plain  copy
  1. public void onBindViewHolder(BindingHolder holder, int position) {  
  2.    final T item = mItems.get(position);  
  3.    holder.getBinding().setVariable(BR.item, item);  
  4.    holder.getBinding().executePendingBindings();  
  5. }  

直接 Binding

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

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


六、属性 Setters

每当绑定值发生变化时,生成的绑定类必须使用绑定表达式在视图上调用 set 方法。Data Binding 框架具有自定义调用哪个方法来设置值的方法

自动安装(Automatic Setters)

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

例如,有关 TextView 的 android:text 属性的表达式会寻找一个 setText(String) 的方法,如果表达式返回一个 int,Data Binding会搜索的 setText(int) 方法,这里要注意:表达式要返回正确的类型,如果需要的话使用 casting,Data Binding 任然会工作,即使没有给定的名称属性存在,然后,你可以通过 Data Binding 轻松地为任何 setter "创造"属性,例如,DrawerLayout 没有任何属性,但大量的 setters,你可以使用自动 setters 来使用其中的一个:
[html]  view plain  copy
  1. <android.support.v4.widget.DrawerLayout  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content"  
  4.     app:scrimColor="@{@color/scrim}"  
  5.     app:drawerListener="@{fragment.drawerListener}"/>  

重命名 Setters(Renamed Setters)

一些有 Setters 的属性按名称并不匹配,对于这些方法,属性可以通过 BindingMethods 注解相关联,还必须与一个包含BindingMethod 注解类相关联,每一个用于重命名的方法,例如  andorid:tin 属性与  setImageTintList 相关联,而不与 setTint相关
[java]  view plain  copy
  1. @BindingMethods({  
  2.        @BindingMethod(type = "android.widget.ImageView",  
  3.                       attribute = "android:tint",  
  4.                       method = "setImageTintList"),  
  5. })  
上面的例子,开发者不太可能重命名编译程序,Android 框架属性已经实现了

自定义 Setters(Custom Setters)

有些属性需要自定义绑定逻辑,例如,对于 android:paddingLeft 属性并没有相关的setter,相反,setPadding (left、top、right、bottom)是存在,一个带有 BindingAdapter 注解的静态绑定适配器方法允许开发者自定义 setter 如何对于一个属性的调用
Android 的属性已经创造了 BindingAdapters,举例来说,对于 paddingLeft:
[java]  view plain  copy
  1. @BindingAdapter("android:paddingLeft")  
  2. public static void setPaddingLeft(View view, int padding) {  
  3.    view.setPadding(padding,  
  4.                    view.getPaddingTop(),  
  5.                    view.getPaddingRight(),  
  6.                    view.getPaddingBottom());  
  7. }  
Binding 适配对其他定制类型非常有用,例如,自定义 loader 可以用异步载入图像
当有冲突时,开发人员创建的 Binding 适配器将覆盖 Data Binding 默认适配器
你也可以创建可以接收多个参数的适配器:
[java]  view plain  copy
  1. @BindingAdapter({"bind:imageUrl""bind:error"})  
  2. public static void loadImage(ImageView view, String url, Drawable error) {  
  3.    Picasso.with(view.getContext()).load(url).error(error).into(view);  
  4. }  

[html]  view plain  copy
  1. <ImageView app:imageUrl="@{venue.imageUrl}"  
  2. app:error="@{@drawable/venueError}"/>  

如果对于一个 ImageView imageUrl 和 error 都被使用,并且 imageUrl 是一个 String 类型以及 error 是一个 drawable 时,该适配器被调用
  • 匹配的过程中自定义 name spaces 将被忽略
  • 你也可以为 Android name spaces 写适配器

绑定适配器方法可以在其处理程序中选择旧值。取旧值和新值的方法应具有所有属性的旧值,其次是新值:
[java]  view plain  copy
  1. @BindingAdapter("android:paddingLeft")  
  2. public static void setPaddingLeft(View view, int oldPadding, int newPadding) {  
  3.    if (oldPadding != newPadding) {  
  4.        view.setPadding(newPadding,  
  5.                        view.getPaddingTop(),  
  6.                        view.getPaddingRight(),  
  7.                        view.getPaddingBottom());  
  8.    }  
  9. }  

事件处理程序只能用一个抽象方法与接口或抽象类一起使用,例如:
[java]  view plain  copy
  1. @BindingAdapter("android:onLayoutChange")  
  2. public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,  
  3.        View.OnLayoutChangeListener newValue) {  
  4.     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {  
  5.         if (oldValue != null) {  
  6.             view.removeOnLayoutChangeListener(oldValue);  
  7.         }  
  8.         if (newValue != null) {  
  9.             view.addOnLayoutChangeListener(newValue);  
  10.         }  
  11.     }  
  12. }  

当监听器有多个方法时,它必须被分割成多个监听器,例如,View.OnAttachStateChangeListener 的方法有两种:onViewAttachedToWindow() 和 onViewDetachedFromWindow()。然后,我们必须创建两个接口来区分它们的属性和处理程序:
[java]  view plain  copy
  1. @TargetApi(VERSION_CODES.HONEYCOMB_MR1)  
  2. public interface OnViewDetachedFromWindow {  
  3.     void onViewDetachedFromWindow(View v);  
  4. }  
  5.   
  6. @TargetApi(VERSION_CODES.HONEYCOMB_MR1)  
  7. public interface OnViewAttachedToWindow {  
  8.     void onViewAttachedToWindow(View v);  
  9. }  

因为改变一个监听器也会影响另一个,我们必须有三个不同的绑定适配器,一个为每个属性和一个为两者,他们应该被设置:
[java]  view plain  copy
  1. @BindingAdapter("android:onViewAttachedToWindow")  
  2. public static void setListener(View view, OnViewAttachedToWindow attached) {  
  3.     setListener(view, null, attached);  
  4. }  
  5.   
  6. @BindingAdapter("android:onViewDetachedFromWindow")  
  7. public static void setListener(View view, OnViewDetachedFromWindow detached) {  
  8.     setListener(view, detached, null);  
  9. }  
  10.   
  11. @BindingAdapter({"android:onViewDetachedFromWindow""android:onViewAttachedToWindow"})  
  12. public static void setListener(View view, final OnViewDetachedFromWindow detach,  
  13.         final OnViewAttachedToWindow attach) {  
  14.     if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {  
  15.         final OnAttachStateChangeListener newListener;  
  16.         if (detach == null && attach == null) {  
  17.             newListener = null;  
  18.         } else {  
  19.             newListener = new OnAttachStateChangeListener() {  
  20.                 @Override  
  21.                 public void onViewAttachedToWindow(View v) {  
  22.                     if (attach != null) {  
  23.                         attach.onViewAttachedToWindow(v);  
  24.                     }  
  25.                 }  
  26.   
  27.                 @Override  
  28.                 public void onViewDetachedFromWindow(View v) {  
  29.                     if (detach != null) {  
  30.                         detach.onViewDetachedFromWindow(v);  
  31.                     }  
  32.                 }  
  33.             };  
  34.         }  
  35.         final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,  
  36.                 newListener, R.id.onAttachStateChangeListener);  
  37.         if (oldListener != null) {  
  38.             view.removeOnAttachStateChangeListener(oldListener);  
  39.         }  
  40.         if (newListener != null) {  
  41.             view.addOnAttachStateChangeListener(newListener);  
  42.         }  
  43.     }  
  44. }  

上面的例子是比正常的稍微复杂,因为 View 使用添加和删除的监听者而不是为 View.OnAttachStateChangeListener 设置方法,android.databinding.adapters.listenerutil 类有助于保持跟踪,他们可能会在绑定适配器删除以前的 listener

七、转换(Converters)

对象转换(Object Conversions)

当从 Binding 表达式返回对象时,将从自动、重命名和自定义 setters 中选择一个配置器。对象将被转换为所选集的参数类型
这是为那些使用 ObservableMaps 保存方便,例如:
[html]  view plain  copy
  1. <TextView  
  2.    android:text='@{userMap["lastName"]}'  
  3.    android:layout_width="wrap_content"  
  4.    android:layout_height="wrap_content"/>  
在 userMap 返回一个对象并且该对象将自动转换为 setText(CharSequence) 的参数类型,当有关参数类型可能混乱,开发人员需要在表达式中转换

自定义转换 Custom Conversions)

有时在特定类型之间应该自动转换。例如,当设置背景:
[html]  view plain  copy
  1. <View  
  2.    android:background="@{isError ? @color/red : @color/white}"  
  3.    android:layout_width="wrap_content"  
  4.    android:layout_height="wrap_content"/>  

这里,背景需要 Drawable 对象,但颜色是一个整数,不管何时有 Drawable 并且返回值是一个整数,那么整数类型会被转换为ColorDrawable 看,这个转换是通过使用带带有 BindingConversion 注解的静态方法完成的:
[java]  view plain  copy
  1. @BindingConversion  
  2. public static ColorDrawable convertColorToDrawable(int color) {  
  3.    return new ColorDrawable(color);  
  4. }  

注意,转换只发生在 setter 级别,所以它不允许混合以下类型:
[html]  view plain  copy
  1. <View  
  2.    android:background="@{isError ? @drawable/error : @color/white}"  
  3.    android:layout_width="wrap_content"  
  4.    android:layout_height="wrap_content"/>  

八、Android Studio 对 DataBinding 的支持(Android Studio Support for Data Binding)

Android Studio 支持 DataBinding 代码的许多代码编辑功能,例如,它支持数据绑定表达式的下列功能:
  • 语法高亮
  • 表达式语言语法错误的标记
  • XML 代码完成
  • 引用和快速文档
注意:数组和泛型类型(如可观察类)可能在没有错误时显示错误

预览窗格显示数据绑定表达式的默认值,如果提供,以下从布局的 XML 文件的元素实例摘录,预览窗格中显示的默认文本值的占位符文本:
[html]  view plain  copy
  1. <TextView android:layout_width="wrap_content"  
  2.    android:layout_height="wrap_content"  
  3.    android:text="@{user.firstName, default=PLACEHOLDER}"/>  

如果你需要你的项目的设计阶段中显示一个默认值,你也可以使用工具的属性而不是默认的表达式的值,在设计 layout 描述属性

原文地址:点击打开链接

如有翻译错误,请指正

猜你喜欢

转载自blog.csdn.net/jiang19921002/article/details/80506787