翻译自:Data Binding

本文档说明了如何使用Data Binding Library来编写声明式布局,并将必要的代码最小化的绑定到应用的逻辑和布局中。

Data Binding Library非常的灵活并且兼容性很广 -- 它是一个兼容包,因此可以将它用到从Android 2.1(API level 7+)开始的所有的android平台版本中。

要使用Data Binding,android 的构建插件gradle要求1.5.0-alpha1或者更高的版本。

构建环境

要开始使用Data Binding ,先从Android SDK manager的兼容库中下载。
下载完成后需要进行配置,添加 dataBinding 节点到app module的build.gradle 中。
android {....dataBinding {enabled = true}
}

如果你的app module依赖于另外一个使用了data binding的库,你的app module 的 build.gradle中也需要进行配置。
另外,确保使用的是兼容版本的Android Studio开发工具。Android Studio 1.3及之后的版本为data binding提供支持描述,Android Studio Support For Data Binding。

Data Binding 布局文件

写数据绑定式

Data-binding的布局文件和普通的布局文件相比,有一些略微的不同;Data-binding的布局文件的根节点是 layout ,随后跟随的是一个 data 元素,再下面才是普通布局元素。此视图元素将是一个非绑定布局文件的根目录。一个简单的示例:
<?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><LinearLayoutandroid: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>

在这个布局中使用 variable 描述数据属性。

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

在布局文件中的数据绑定表达式是写入到节点的属性中的,语法为:"@{}";这里TextView的text设置为用户的firstName。

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

数据对象

假设现在有一个普通的java对象(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;}
}

这种类型的对象的数据不会发生更改。在应用程序中,有一次读取的数据是很常见的,此后数据不能更改。也可以使用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;}
}

从数据绑定的角度看,这两个文件是一样的。表达式 @{user.firstName} 作用于 TextView的 android:text="" 属性上, 对于前一个文件将使用 firstName 字段,对于后一个文件将使用 getFirstName 方法获取需要绑定的数据。或者,将会解析 firstName() 方法。

通俗的讲:我们的数据实体对象的属性可以是public类型的,如果是其它类型的则需要配置getter方法,或者直接使用字段名作为方法名的函数。

绑定数据

默认的绑定类的名称将根据布局文件名称来生成,使用PascalCase(帕斯卡写法)拼接上Binding后缀。上述布局文件是main_activity.xml所以生成类是MainActivityBinding。
这个类包含所有绑定的布局属性(例如:user 变量)和知道如何分配值到绑定表达式。创建绑定的最简单方法是inflating:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);User user = new User("Test", "User");binding.setUser(user);
}

做好了后,运行程序,会发现测试的用户数据显示到了界面上。或者也可以通过get view的方式创建绑定类。

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

如果是在ListView或者RecyclerView的item中使用数据绑定,则使用下面的方式:

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

绑定事件

事件可以直接绑定到方法上,类似于通过布局文件直接绑定方法( android:onClick ),事件属性名是由侦听器方法的名称来管理的,有一些例外。例如:View.onLongClickListener 有一个方法 onLongClick,因此事件的属性是 android:onLongClick。
要将事件分配给它的处理程序,使用一个标准的绑定表达式,以方法名称作为值调用。例如,如果你的数据对象有2个方法:
public class MyHandlers {public void onClickFriend(View view) { ... }public void onClickEnemy(View view) { ... }
}

绑定表达式可以指定一个View的OnClickListener:

<?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><LinearLayoutandroid: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>

有一些单独的事件,为了避免和 onclick 冲突,有专门的属性,以下表格做了列举:

Class(类) Listener Setter(类中设置监听器的方法) Attribute(属性)
SearchView setOnSearchClickListener(View.OnClickListener)           android:onSearchClick     
ZoomControls            setOnZoomInClickListener(View.OnClickListener)      android:onZoomIn
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut

布局明细

导包

在 data 节点可以有0个或者多个导入节点,这样你就能像在java中,使用另外一个类一样的方便。
<data><import type="android.view.View"/>
</data>

如上面,通过 import 导入指向的View后,就可以直接在绑定表达式中使用 View 了。

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

就如果上面,如果在 data 节点下使用了 import 导入了指向的View,就可以在android:visibility中使用View了。就跟在java代码中的使用一样。
如果导入了多个类,并且出现了命名冲突的情况,可以通过指定别名【alias】的方式来解决。

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

如上,导入了两个类,结果两个类的名称都是View,这个时候,就为下面的类指定别名为 '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<User>"/>
</data>

注:Android Studio针对 import 还没有自动补全功能。您的应用程序仍然可以编译通过,你可以在IDE的问题在你的变量定义使用完全限定的名称。

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

导入的类,也可以在表达式中,使用静态的字段和方法(static):

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

就像在java中,java.lang.*是自动导入的。

变量

在 data 节点下可以有任意数量的变量(variable),每一个 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* 接口,将不被观察到的变量!

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

生成绑定类将有一个为每个描述变量的setter和getter。这些变量在调用setter之前,将采用默认值 - 引用类型为null,int为0,boolean为false等。

一个特殊的变量命名为的context。context的值是从根目录的getcontext()语境。context变量将具有该名称的显式变量声明重写。

自定义绑定类名

默认的,绑定的类名是根据布局文件的名称来生成的,大写字母开头,去除 '_',然后拼接上 '_' 后的单词,然后跟上 ‘Binding’ 。这个类将被放置在一个 module 包的 databinding 包下。例如:如果一个布局文件的名称是,contact_item.xml生成的绑定文件名称是 ContactItemBinding。如果模块包为:com.example.my.app然后生成的绑定类将被放到 com.example.my.app.databinding 。
绑定类的名称,也能通过 data 节点的 class 属性重命名或者放到不同的包下面。

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

这个例子生成的绑定类名是ContactItem,并且放到module包的databinding下面。如果想要放到不同的包下面,可以通过使用 '.' 前缀:

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

在这个例子中,生成的绑定类 ContactItem将被放到module的包目录下,如果指定了全类名,可以放到任意包下:

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

Includes

变量可以通过应用程序的命名空间和属性变量的方式,将变量传递给通过 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><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/name"bind:user="@{user}"/><include layout="@layout/contact"bind:user="@{user}"/></LinearLayout>
</layout>

在这个例子中,在 name.xml 和 contact.xml 布局文件中都能使用 user 变量。
Data Binding 不支持 include 作为 merge 节点的直接子类的方式,例如,以下示例是不支持的

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

Expression Language(表达式·)

Common Features(通用的特点)

绑定表达式跟java表达式一样,有一些通用的符合:
  • 数学符合(Mathematical) : +  -   *   /   %
  • 字符串拼接符合(String Concatenation):+
  • 逻辑运算符(Logical):&&【并且】   ||【或者】
  • 位操作符(Binary):&【与】  |【或】  ^【异或】
  • 单目运算符(Unary):+【取正,正号】  -【取负,负号】  !【非】  ~【取反】
  • 位运算符(Shift):>>【带符合右移】  >>>【无符号右移】  <<【带符号左移】
  • 比较运算符(Comparison):==【等于】  >【大于】 <【小于】 >=【大于等于】 <=【小于等于】
  • instanceof运算符
  • 分租(Grouping):()【小括弧】
  • 常量(Literals):character(字母)、String(字符串)、numeric(数字)、null
  • 数据类型转换(Cast)
  • 方法调用(Method Calls)
  • 字段调用(Field Access)
  • 数组调用(Array access):[]
  • 三目运算符:? :【类似于if-else】
例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

缺少的操作

有一些操作在表达式语法中是没有的,你可以在java代码中使用。

  • this
  • super
  • new
  • 显示泛型调用

Null合并运算符

Null合并运算符(??)类似于三目运算符(? : )【if-else】
android:text="@{user.displayName ?? user.lastName}"

上面的代码等价于:

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

属性引用(Property Reference)

在上面一开始讲的时候,就讲过通过表达式对于实体对象数据的引用,对于字段(fields)、getters、ObservableFields的引用和普通对象数据的引用是一样的。

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

避免空指针异常

Data Binding会自动检查null,避免出现空指针异常;例如:如果表达式为 @{user.name},如果user为空,则user.name则会赋予默认值(null),如果 @{user.age},age字段是int型的,则默认值是0。

集合

常用的集合:arrays, lists, sparse lists, 和 maps都可以通过 [] 访问。
<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]}"

字符串

当使用单引号作为属性引用的时候,其中的字符串可以使用双引号:

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

属性引用使用双引号,也是可以的,这个时候,字符串的引用则通过 &quot;或者 `【键盘上1前面的那个符合(不按shift)】来引用。

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

Resources(资源)

使用正规的表达式来访问资源文件也是可以的:

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

格式字符串和复数可提供参数:

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

当一个复数需要多个参数时,所有参数都应该通过:

  Have an orangeHave %d orangesandroid: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.

当这些observable数据对象被绑定到UI界面时,数据对象的属性发生更改时,UI也将自动更新。

Observable 对象(Observable Objects)

一个类实现 Observable 接口时,允许添加一个监听器到绑定的对象上,监听数据的变化。
Observable 接口提供了一个添加和删除监听器的机制,但通知是由开发人员决定的。为了使开发更容易,Data Binding提供了一个基类 -- BaseObservable 是为了实现侦听器注册机制而创建的。当数据属性发生变化时数据实现类依然负责进行通知,这是通过给getter方法指定一个 Bindable 注解,然后在setter中进行通知来完成的。
private static class User extends BaseObservable {private String firstName;private String lastName;@Bindablepublic String getFirstName() {return this.firstName;}@Bindablepublic 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文件将在模块包中(module package)生成。如果数据类不能被更改, Observable 接口通过方便的PropertyChangeRegistry来实现用于储存和有效地通知监听者。

Observable字段

一些小工作会涉及到创建Observable类,因此一些开发者想节省时间或者有少量的字段的可以使用 ObservableField 和  ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloat,ObservableDouble, and ObservableParcelable.ObservableFields是自包含具有单个字段的 ObservableField 对象。原始版本避免装箱和拆箱过程中访问操作。要使用ObservableField,需要在类中创建public 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 集合

一些应用程序使用更多的动态结构来保存数据。Observable允许键访问这些数据对象,也就是说Observable是通过key-value的方式存储数据的。类似于java中的Map和List。ObservableArrayMap 使用key是引用类型的,例如:String,类似于java中的Map。

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

在layout布局文件中,可以通过String键访问map:

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

ObservableArrayList 用于key是整数的情况,类似于java中的List:

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

在layout布局文件中,通过索引访问list:

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

生成Binding类

生成的绑定文件指向了布局中的变量与视图。就如前面所讲的一样,生成的绑定类可以自定义名称和包名。所有生成的绑定类都扩展自ViewDataBinding

创建(Creating)

应该在inflation之后创建,以确保在布局中与表达式的视图结合之前不干扰视图层次结构。有几个方法可以绑定到布局。最常用的是使用绑定类的静态方法。从加载View层次结构到绑定只需要一步。可以通过只需要一个LayoutInflater的方式绑定,也可以再传递ViewGroup的方式:

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

如果布局是使用不同的结构,它可能是单独的:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时候不能提前知道绑定类,这种情况下,可以通过 DataBindingUtil 类来进行获取绑定:

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

带id的View

Data Binding对于每一个布局文件中带id的View都会生成一个public final 的 View字段。Binding只是在页面层次结构上做简单的传递,提取带id的View。这种机制在某些情况下比传统的findViewById要快,例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="user" type="com.example.User"/></data><LinearLayoutandroid: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}"<span style="white-space:pre"> </span>   android:id="@+id/firstName"/><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.lastName}"<span style="white-space:pre">   </span>   android:id="@+id/lastName"/></LinearLayout>
</layout>

生成的绑定类如下:

public final TextView firstName;
public final TextView lastName;

对于Data Binding并不是完全没有必要再去获取一个View实例,再某些时候也需要去访问View。

Variables

对于每一个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>

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

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 跟普通的视图有一些不同。他们开始时并不是可见的,当明确要显示或被载入时,通过加载另一个布局文件来替换自己。

因为ViewStub基本是隐藏的,所以在Data Binding中,ViewStub也是隐藏的。在Data Binding中,当ViewStub被加载完成后,一个ViewStub将被转换为一个ViewStubProxy,供开发者访问ViewStub。

当加载另一个文件的时候,必须建立新的布局。因此,ViewStubProxy 必须监听 ViewStub的 ViewStub.OnInflateListener 事件。由于只有一个视图存在,这ViewStubProxy允许开发人员建立一个onInflateListener监听它。

<layout xmlns:android="http://schemas.android.com/apk/res/android"><LinearLayout...><ViewStubandroid:id="@+id/view_stub"android:layout="@layout/view_stub"... /></LinearLayout>
</layout>

布局文件中添加一个i ViewStub 并且添加id属性。

binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {@Overridepublic void onInflate(ViewStub stub, View inflated) {ViewStubBinding binding = DataBindingUtil.bind(inflated);User user = new User("fee", "lang");binding.setUser(user);}
});

在 Java 代码中获取 binding 实例,为 ViewStubProy 注册 ViewStub.OnInflateListener 事件。

高级Binding(Binding进阶)(Advanced Binding)

动态变量(Dynamic Variables)

有时候,并不知道明确的绑定类。例如:RecyclerView.Adapter对于layout布局的操作并不知道具体的绑定类,它仍然需要在onBindViewHolder(VH, int)中绑定值。

在下面这个例子中,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();
}

立即绑定(Immediate Binding)

当一个变量或Observable变化时,该绑定将在下一帧之前更改。然而有时候,当想要立即绑定更改的时候,可以调用 executePendingBindings()强制执行

后台线程(Background Thread)

你可以在后台线程中改变你的数据模型,只要它不是集合。数据绑定将本地化每个变量(Variable)/字段(Field),同时进行评估,以避免任何并发问题。

属性设置(Attribute Setters)

每当一个绑定值的变化,Data Binding生成的绑定类一定会调用配置了绑定表达式的View相关属性的setter方法。Data Binding框架有自定义设置值的方法。

Automatic Setters

对于一个属性,Data Binding框架会尝试去寻找相应的setter方法。与该属性的namespace(命名空间)并不什么关系,仅仅与属性本身名称有关。例如:在TextView的 android:text配置了绑定表达式过后,框架会去寻找setText(String)方法。如果绑定表达式返回的值是int类型的,框架会寻找 setText(int) 方法。确保表达式返回正确的数据类型,如果需要的话可以使用数据类型转换。注意:即使没有属性存在于给定的名称,Data Binding也会正常运行。您可以很轻松的使用Data Binding框架为一些 setter 方法创建属性。例如:对于 support v4包中的 DrawerLayout 并没有任何的属性,但是有很多的setter方法。您可以自动的去使用这些setter方法:
<android.support.v4.widget.DrawerLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:scrimColor="@{@color/scrim}"app:drawerListener="@{fragment.drawerListener}"/>

重命名Setter(Renamed Setters)

有一些属性的setter方法并不能通过名称来匹配。对于这些方法,属性可以通过 BindingMethods注解来进行关联。每一个用于重命名的方法都必须与一个包含 BindingMethods 注解的类相关联。例如:对于android:tint属性,与之相关联的setter方法是setImageTintList(ColorStateList)并不是setTint。

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

实际开发过程中,开发人员想要重命名这些属性是不可能的,因为android底层的属性都已经被实现了。

自定义Setter方法(Custome Setters)

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

android的属性已经创建了BindingAdapter,例如:对于paddingLeft属性:

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

Data Binding适配器可以用于定制其它类型。例如,可以自定义loader方法用来在线程中加载图片:

当有冲突时,开发人员创建的绑定适配器将重写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}”
<span style="white-space:pre"> </span>app:error=“@{@drawable/venueError}”/>

对于一个ImageView,如果同时存在imageUrl和error并且imageUrl为String类型,error是drawable类型适配器将被调用:

  • 自定义的命名空间(namespace)将忽略匹配
  • 同时也可以为命名空间(namespace)编写适配器

绑定适配器方法可以选择在其处理程序中选择旧值。一个操作旧值和新值的方法,首先应该有属性的所有旧值,其次要有新值:

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

事件处理程序可以只使用一个接口(interface)或抽象类的抽象方法,例如:

@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() {@Overridepublic void onViewAttachedToWindow(View v) {if (attach != null) {attach.onViewAttachedToWindow(v);}}@Overridepublic 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的设置方法。android.databinding.adapters.ListenerUtil类帮助追踪以前的监听器,因此,他们可能会在绑定适配器中删除。
通过在OnViewDetachedFromWindow和OnViewAttachedToWindow接口上使用@TargetApi(VERSION_CODES.HONEYCOMB_MR1),Data Binding生成的代码知道监听器只有运行在Honeycomb MR1和更新的设备上才生成。相同的版本支持addOnAttachStateChangeListener(View.OnAttachStateChangeListener).

转换(Converts)

对象转换(Object Conversions)

当从数据表达式返回数据对象时,将从自动,重命名以及自定义的setter方法中进行选择。数据对象将转换为setter方法的参数类型。

这是为了方便使用 ObservableMap 来保存数据,例如:

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

userMap 返回的对象将被自动转换为找到的text的setter方法setText(CharSequence)的参数的数据类型。当有关的参数可能产生类型混乱时,就需要开发人员在表达式中进行转换。

自定义转换(Custom Conversions)

有时候转换为自动的在特定的类型之间。例如:当设置背景颜色时:

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

在这里设置背景需要的是Drawable,但是颜色color是一个integer(整型)的。每当一个Drawable返回的是整形(int)的时候,int应该被转换为ColorDrawable类型。这个转换是通过使用带有BindingConversion注解的静态方法完成的:

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

注意,转换只发生在setter级,所以它是不允许这样的组合类型:

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

Android Studio 支持 Data Binding

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

  • 语法高亮
  • 标记表达式语言的语法错误

  • XML代码实现

  • 引用,包括导航(如导航到声明)和快速文档

  • 注意:数组(集合)、泛型以及 Observable 类,在没有错误的时候,也可能显示错误。

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

如果你需要你的项目的设计阶段中显示一个默认值,你也可以使用工具属性来替换默认的表达式的值,详细描述请看: Designtime Layout Attributes。

Data Binding 指南相关推荐

  1. WPF中的Data Binding调试指南

    点击蓝字"大白技术控"关注我哟 加个"星标★",每日良时,好文必达! WPF中的Data Binding如何Debug? 大家平时做WPF开发,相信用Visua ...

  2. Data Binding 用户指南(Android)

    1. 介绍 这篇文章介绍了如何使用Data Binding库来写声明的layouts文件,并且用最少的代码来绑定你的app逻辑和layouts文件. Data Binding库不仅灵活而且广泛兼容- ...

  3. Android开发教程 - 使用Data Binding(二)集成与配置

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  4. XAML数据绑定(Data Binding)

    XAML数据绑定(Data Binding) Data Binding可以使得XAML标签属性的赋值更为灵活和方便.在绑定过程中,获取数据的标签成为目标标签:提供数据的标签成为源标签.在XAML中,一 ...

  5. Data Binding Library数据绑定框架

    Data Binding Library是Google在2015年IO大会上发布的一个用于实现MVVM设计模式的支持库 环境配置 在Android Studio 2.0 原生支持Data Bindin ...

  6. android 高级项目,从零开始的Android新项目8 - Data Binding高级篇

    承接上篇,本篇继续讲解一些Data Binding更加进阶的内容,包括:列表绑定.自定义属性.双向绑定.表达式链.Lambda表达式.动画.Component注入(测试)等. 列表绑定 App中经常用 ...

  7. 谷歌推荐Data Binding实现MVVM模式(完整文档)

    Data Binding 类库 这篇文档将教你如何运用 Data Binding 类库来编写声明试布局,并且尽量减少粘合代码对你的应用逻辑和布局上的绑定. Data Binding 是一种灵活和广泛兼 ...

  8. 「最简单」的 Core Data 上手指南

    本文讲的是「最简单」的 Core Data 上手指南, 原文地址:The Easiest Core Data 原文作者:Alberto De Bortoli 译文出自:掘金翻译计划 译者:Zheaol ...

  9. WPF中的数据绑定Data Binding使用小结

    完整的数据绑定的语法说明可以在这里查看: http://www.nbdtech.com/Free/WpfBinding.pdf MSDN资料: Data Binding: Part 1 http:// ...

  10. Data Binding的使用总结

    2019独角兽企业重金招聘Python工程师标准>>> 个人使用Data Binding 框架,觉得代码很简洁,解耦性很强,下面简单介绍使用方法 首先在项目gradle中添加如下代码 ...

最新文章

  1. 【OpenCV 4开发详解】形态学应用
  2. SqlDataAdapter的使用注意事项
  3. 获取指定个数指定范围的随机数
  4. [Leedcode][JAVA][第892题][图形题]
  5. 百度平台K12人群洞察报告
  6. 操作系统(8)-存储系统
  7. 如何更新Word文档的附图或附表序号
  8. base64转化字节流 js_js - blob流和base64,以及file和base64的相互转换
  9. 手机所有录音功能失效_疯狂打CALL!小米手机自带扫描文字功能,一秒识别所有文字信息!...
  10. 硬盘分区后的逻辑结构
  11. 多媒体技术计算题、操作题
  12. 关于flash强制更新:早上上班,多台电脑提示未安装flash
  13. 如何计算芯片的ESP mac 地址
  14. linux shell提示文件不存在,Bash检查是否显示文件不存在?
  15. 计算机wps文字基础知识,计算机一级考试WPS基础练习题(含答案)
  16. php 招聘要求 转载
  17. 鼠标不能再Linux命令界面滚动,在linux中,鼠标的滚轮怎么无法使用
  18. 给网站开发者推荐18个在线手册,值得收藏
  19. Linux串口测试工具
  20. 菜鸟程序员试用期指南,职场新人必备法则

热门文章

  1. 年终将至,财务人如何做好数据分析?
  2. Android性能优化典范-第2季
  3. docker镜像下载的网站
  4. 再分享一个零成本做文库代下载赚钱项目
  5. 领睿s1pro的黑苹果EFI及黑苹果教程
  6. MATLAB线条颜色
  7. 麻省理工18年春软件构造课程阅读11“抽象函数与表示不变量”
  8. linux常用运行库,软件常用运行库-软件常用运行库scku下载 v3.1.0.0官方版--pc6下载站...
  9. Ubuntu18.04安装TIM、微信
  10. SQL正则表达式、列表运算、涉及null的查询