Android Data Binding框架指南

本文翻译自Google官方文档http://developer.android.com/tools/data-binding/guide.html,如有疏漏或错误,欢迎提出意见和建议

这份文档阐述了如何使用Data Binding Library来编写申明式的布局文件和减少应用逻辑和布局之间必要的绑定代码。

Data Binding Library以支持包的方式发布,具有灵活性和广泛的兼容性,因此你可以在Android2.1(API level 7+)及以上的版本中使用这个框架

使用Data Binding Library需要使用 Android Plugin for Gradle 1.5.0-alpha1或者以上版本的gradle插件


1 构建环境

使用data binding框架,需要使用Android SDK manager中从支持库中下载相应的支持包,然后在你的应用中配置使用data binding框架,即在你的应用构建脚本build.gradle中添加dataBinding元素,示例如下:

android {
    ...
    dataBinding {
        enabled = true
    }
}

如果你的应用依赖一个使用了data binding框架的library,那么,你页需要在你的应用的构建文件中添加上述配置。

另外,确保您使用的是已经支持了data binding的Android studio,即,需要升级到Android studio 1.3 或者更高版本。


2 数据绑定布局文件

2.1 编写第一个data binding表达式

Data-binding布局文件与普通的布局文件相比,有一些细微的差别,体现在:

  1. 布局文件的根节点是layout
  2. layout里面需要一个data元素和一个view根节点,view是就是普通的android的布局文件

示例如下:

<?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 name="user" type="com.example.User"/>

这个user变量可以被view使用

在布局文件中的,可以使用表达式”@{}”来读取变量的属性,在上例中,TextView的文本被设置成user的firstName:

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

2.2 数据对象

假设你有一个POJO风格的类User:

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

或者是JavaBean风格的类User:

public class User {
    private final String firstName;
    private final String lastName;
    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public String getFirstName() {
        return this.firstName;
    }
    public String getLastName() {
        return this.lastName;
    }
}

从data binding的角度来看,上述两个类是等同的,TextView的属性android:text可以通过表达式@{user.firstName}访问第一个类型的对象的firstName属性,也可访问第二个类型的对象的getFirstName()方法,同时,它还可以访问firstName()方法,如果存在这样的方法的话。

2.3 绑定数据

默认地,AS会在布局文件的基础上产生一个绑定者类,其名字遵循Pascal 格式然后以Binding为后缀,在上面的例子中,布局文件的名字是main_layout.xml,因此生成的类名就是MainActivityBinding。这个类包含了布局文件中变量属性和控件属性的绑定关系。创建绑定关系最简单的方式如下:

@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的属性就展示在TextView中了。也可以使用另外一种方式去得到这个绑定关系:

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

当你在ListView或者RecyclerView中使用data binding的时候,你需要使用下面的方式来获取绑定关系:

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

2.4 绑定事件

各种事件可以直接绑定到处理方法上,这与android:onClick相似,可以将空间的click事件指派给activity的一个方法一样。大部分的事件属性名称与监听者的响应方法的名称一致,也有少许例外。例如,View.OnLongClickListener 有一个onLongClick()方法,因此事件的属性就是android:onLongClick.

要将一个事件指派给一个处理者,可以使用正常的绑定表达式,值就是要调用的方法名称。例如,你的数据对象有两个方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
    public void onClickEnemy(View view) { ... }
}

绑定表达式可以为控件将click事件指派给MyHandlers如下:

<?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 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="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
               android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
   </LinearLayout>
</layout>

一些特殊的控件的点击事件并不是由OnClickListener处理,因此由其他属性来配置,此时就需要配置到其他的属性里,如下表:

ClassListener SetterAttribute
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

3 布局文件细节

3.1 导入

data元素可以包含0个或多个import子元素,每个import元素可以引入1个class,引入之后,布局文件就可以像在java文件里一样,访问被引入的class,例如:

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

在layout的data如上代码,那么在layout的view中就可以访问android.view.View类

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

上面TextView中,android:visibility属性中引用了View.VISIBLE 和 View.GONE

如果引入的两个类的名字存在存在,那么可以使用alias来给冲突的类起别名

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

这样,在该布局文件内View就指的是android.view.View,Visita指的是com.example.real.estate.View

类引入之后,就可以作为变量的类型:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
</data>

备注: 目前AS还不支持处理imports,因此变量的自动不全可能还不能正常工作,但是这并不影响程序的正常编译。一个解决办法是定义变量时使用类的全名.

也可以参与表达式运算:

<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.*

3.2 变量

data 元素可以包含任意多个variable子元素,每个variable子元素描述了一个可以向这个layout设置的用于绑定表达式的属性。

<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>

上面定义了3个变量,以及它们的类型,在数据绑定的时候,外部为这3个变量赋值。

变量的类型在编译的时候进行检查,因此一个变量是否实现了Obserable或者是一个Obserable Collection反应在type属性。如果一个变量没有实现Observable系列的接口,哪些这个变量就不会被观察!(即绑定之后,这个变量数据发生变化时不会反应到空间上)

当在不同配置(如横竖屏)时有不同布局文件,这些变量会被合并在一起,因此这些布局文件之间不能有冲突的变量定义。

生成的binding class 会为每一个变量创建一个setter和getter方法,变量的默认值规则与java的默认值规则一致

一个特殊的变量context也会被创建,它的值是根空间的getContext(),如果显示申明了context这个变量,那么默认的context变量会被重载

3.3 自定义Binding名称

Binding class 的默认名字是基于layout的名字,即首字母大写,去掉”_”,然后以Binding结尾,这个class会放工程的在databinding包下面,例如,布局文件contact_item.xml将为生成ContactItemBinding,如果工程的包名是com.example.my.app,那么这个ContactItemBinding将会放到com.example.my.app.databinding下面。

Binding class 的名字也可以定义,有一下的三种自定义方式:
方法一:

<data class="ContactItem">
    ...
</data>

生成Binding class : com.example.my.app.databinding.ContactItem
方法二:

<data class=".ContactItem">
    ...
</data>

生成Binding class : com.example.my.app.ContactItem
方法三:

<data class="com.example.ContactItem">
    ...
</data>

生成Binding class : com.example.ContactItem

3.4 包含

布局文件中的变量可以通过命名空间何变量名作为属性名称传递到被包含的布局文件中去。例如:

<?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必须要包含一个名称为name的变量。

但是,data binding并不支持向直接嵌入merge的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>
   <merge>
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </merge>
</layout>

(注:上面的include作为merge的直接子孩子,因此在该布局中申明的变量不能传递到被包含的布局中去)

3.5 表达式语言

3.5.1 一般功能

表达式语言非常像java语言,下面的表达式与java一致:

  1. 数据运算符 +-*/ %
  2. 字符串连接符 +
  3. 逻辑运算符 && ||
  4. 位运算符 & | ^
  5. 一元运算符 + - ! ~
  6. 移位运算符 >> >>> <<
  7. 比较运算符 == > < >= <=
  8. 实例判断 instanceof
  9. 括号 ()
  10. 字面字符串,数字,null
  11. 类型转换()
  12. 方法调用
  13. 成员调用
  14. 数组访问
  15. 三元运算符 ? :

举例如下:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
3.5.2 缺省操作

也有少量的表达式是缺省的,可以直接使用,如下

  1. this
  2. super
  3. new
  4. Explicit generic invocation(注:显示调用即.符号)
3.5.3 空联合操作符

?? 是空联合操作符,它的含义是如果左边不为空则取左边的值,否则取右边的值,如下:

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

上面的表达式,等价于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
3.5.4 属性引用

上过已经讨论过,当表达式要引用一个类的成员变量时,使用相同的格式去访问成员变量,getter和ObservableFields,如下:

android:text="@{user.lastName}"
3.5.5 空保护

自动生成的Binding自动检查空指针从避免空指针异常, 例如表达式@{user.name}中,如果user为空,则user.name则返回其默认值null,而如果user的成员age为int型,表达式@{user.age}则返回其默认值0

3.5.6 集合

常用的集合:数组,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]}"
3.5.7 字面字符串

当需要在表达式中使用字面字符串,其需要用双引号括起来,此时需要将xml属性值的双引号换成单引号,如下:

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

或者使用反引号来标识字面字符串:

android:text="@{map[`firstName`}"
android:text="@{map[`firstName`]}"
3.5.8 资源

可以使用正常的语法来访问资源:

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)}"

一些资源需要明确的类型运算,如下表:

TypeNormal ReferenceExpression Reference
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList

4 数据对象

任何POJO都可以用于data binding,但是对POJO的修改并不能导致UI刷新,data binding真正强大的地方是可以给数据对象提供在其发生变化时通知界面刷新的的能力。共有三种不同的数据变化通知机制,即Observable Objects,ObservableFields,Observable Collections.当上面的任何一种数据绑定到控件之后,只要他们的一个属性发生变化,就可以自动更新控件。

4.1 Observable Objects

一个类实现了android.databinding.Observable接口,就允许binding添加一个监听者到该数据对象去监听它的属性的变化。

Observable接口定义了添加和移除监听者的机制,但是通知时机是取决于开发者的,为了是开发变得更加的容易,提供了一个基类,android.databinding.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);
   }
}

4.2 ObservableFields

创建Observable类还是需要耗费一些时间的,因此想要节省时间或者只需要少量属性的情况下,可以使用ObservableField和他们的兄弟类,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble和ObservableParcable

ObservableField是拥有单一成员的自包含的可观察的对象。基本数据版本的ObservableField可以避免访问过程中的装箱和拆箱,想要使用ObservableField,创建一个public final的ObservableField成员即可:

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();

4.3 Observable Collections

Some applications use more dynamic structures to hold data. Observable collections allow keyed access to these data objects. ObservableArrayMap is useful when the key is a reference type, such as String.
一些应用使用更加动态的数据结构来保存数据,Observable collections允许使用关键字来访问这些数据对象。当关键字是饮用类型时,如字符串时,ObservableArrayMap就非常有用了。

ObservableArrayMap<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:
在布局中可以通过keys来访问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"/>

当关键字是整数时,可以使用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<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"/>

5 创建绑定者

产生的Binding类将布局中的变量和布局中的控件联系在一起,前面讨论过,Binding的名字和包名可以定制。产生的Binding类都继承自ViewDataBinding。

5.1 创建

View inflated之后需要很快创建binding以确保布局中的表达式绑定到控件之前视图层级不会被打扰。有几种方法可以绑定到布局。最常用的方式是使用Binding类的静态方法。inflater方法inflate视图层级同时一次性的绑定数据,第二个简单一点的方法只接受一个LayoutInflater和第三个接受一个LayoutInflater 和ViewGroup的方法:

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

如果布局是用另外的方法产生的,就需要单独绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时binding关系不能提前知道,在这种情况下,binding可以用DataBindingUtil类来创建:

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

5.2 Views With IDs

布局中有ID的每一个控件都会在Binding中创建一个public final的成员。Binding只遍历一次视图层级,抽取有其中有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>

将会产生一个拥有下面成员的binding类:

public final TextView firstName;
public final TextView lastName;

在没有DataBinding的情况下,IDs几乎没有必要,但是在某些场景下也需要使用代码来访问控件。

5.3 变量

每一个变量会生成对应的访问方法

<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>

将会在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);

5.4 ViewStubs

ViewStubs are a little different from normal Views. They start off
ViewStub与普通的View有一些差别,他们开始是不可见的,当他们变成可见或者显式inflat的时候,他们通过inflate其他布局产生的控件来替换自己。
由于ViewStub实质上会会从视图层级中消失,Binding对象中的View也必须消失以便于回收。因为Views是final型的,一个ViewStubProxy将代替ViewStub,它赋予开发者访问在ViewStub存在时访问ViewStub的权限以及在ViewStub inflate之后,访问替代ViewStub的视图层级。
当inflate另一个layout的时候,必须为新的布局建立一个binding,因此,ViewStubProxy必须监听ViewStub的onInflateListener,同时建立binding关系。因为只会存在一个,ViewStubProxy允许开发者设置一个OnInflaterListener,当建立binding之后,将会回调改接口。

5.5 高级 Binding

5.5.1 动态变量 Variables

有时我们并不能知道具体的绑定关系,例如,一个RecyclerView.Adapter在处理任意布局时并不知道具体的绑定关系,但它仍然需要需要在onBindViewHolder中分配binding值。在这种情况下,与RecyclerView绑定的所有布局都有一个item变量,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();
}
5.5.2 立即Binding

当一个变量或者observable变化时,在下一帧之前会添加修改binding的任务调度。尽管如此,有时我们需要立即执行新的binding,此时可以使用executePendingBindings方法来要强制执行新的binding。

5.5.3 后台线程处理

你可以在后台线程中修改数据模型的值,只要它不是一个集合,Data binding在计算时使用局部变量来避免可能的并发问题。


6 属性访问者

任何时候一个绑定的数据发生变化时,产生的绑定类必须调用View的一个setter方法来传递表达式的值。data binding框架有好几种方法来定制调用View的哪一个方法来设置值。

6.1 自动访问者

对于一个属性,data binding会尝试寻找对应的setAttribute方法。属性的命名空间并没有关系,只匹配该属性的名字本身。

例如,一个表达式与TextView的android:text属性关联,则会查找TextView的setText(String)方法。如果一个表达式返回的值是int型的,data binding会查找setText(int),需要注意保证express返回正确的数据类型,在必要时需要强制转换。需要注意的是,data binding会正常工作即便给定名字的属性并不存在。使用data binding你可以很轻易的为任何的setter方法”创建”一个属性。例如,support包的DrawerLayout没有属性,但是却有很多的setter方法,你可以使用自动setter去调用这些方法。

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

6.2 重命名访问者

有些属性的setter方法与属性的名称并不是一致的。对于这些方法,属性与setter可以使用BindingMethods注解关联在一起。这必须与一个类关联在一起,同时包含BindingMethod注解,每一个重名方法的都需要一个这样的注解。例如,android:tint熟悉实际上与setImageTintList(ColorStateList)关联在一起的,而不是setTint

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

})
开发者并不需要重命名setter,android framework的属性已经实现了。

6.3 自定义访问者

有些属性需要自定义binding逻辑,例如,android:paddingLeft属性并没有关联的setter方法,而是通过使用setPadding(left, top, right, bottom)来设置值。有一个静态的带有BindingAdapter注解的binding 适配器方法允许开发者自定义如何调用一个属性的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 adapters 对其他类型的自定义也是非常有用的,例如,一个自定义的loader 可以通过子线程去加载一张图片。
当存在冲突时,开发者创建的binding adapter会重写系统默认的adapter。

你也可以创建有接受多个参数的adapter。

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

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

这个adapter被调用的条件是一个ImageView同时使用imageUrl和error两个属性,且imageUrl是一个字符串,error是一个drawable。

再匹配的时候,自定义的命名空间会被忽略。你也可以为android命名编写adapter方法。

Binding adpter方法可能会选择的使用旧的值,一个接受旧的和新的值的方法需要将旧的方法放在第一个参数,新的值放在第二参数。

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

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

Event handlers may only be used with interfaces or abstract classes with one abstract method. For example:

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

当一个监听着有多个方法时,它必须被拆分成多个监听者。例如,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);
}

因为改变一个监听者会影响到另外一个,我们必须定义三个不同的adapter,每个属性一个adapter,和一个同时给两个属性赋值的adapter。

@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来操作listener,而不是一组操作View.OnAttachStateChangeListener的方法。android.databinding.adapters.ListenerUtil类帮助跟踪先前的监听者,这样它们可以从Binding Adapter种移除。

By annotating the interfaces OnViewDetachedFromWindow and OnViewAttachedToWindow with @TargetApi(VERSION_CODES.HONEYCOMB_MR1), the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).


7 转换器

7.1 对象转换

7.2 自定义转换

7.3 AS支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值