# DataBindingDemo
**Repository Path**: wangweijun123/DataBindingDemo
## Basic Information
- **Project Name**: DataBindingDemo
- **Description**: No description available
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-08-14
- **Last Updated**: 2021-08-14
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 写在前面
要学习新东西,最好的办法是先学会如何使用。所以,本文仅作 Android Data Binding 的介绍并结合 [DataBindingDemo][DataBindingDemo] 来理解它的用法,后续再对其原理进行深入探讨。
## 简介
Data binding 在2015年7月发布的Android Studio v1.3.0 版本上引入,在2016年4月Android Studio v2.0.0 上正式支持。目前为止,Data Binding 已经支持双向绑定了。
Databinding 是一个实现数据和UI绑定的框架,是一个实现 MVVM 模式的工具,有了 Data Binding,在Android中也可以很方便的实现MVVM开发模式。
Data Binding 是一个support库,最低支持到Android 2.1(API Level 7+)。
Data Binding 之前,我们不可避免地要编写大量的毫无营养的代码,如 findViewById()、setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的毫无营养的代码了。
## 构建环境
1. 首先,确保能使用Data Binding,需要下载最新的 Support repository。否则可能报错,如图:

2. 在模块的build.gradle文件中添加dataBinding配置
android {
....
dataBinding {
enabled = true
}
}
> 注意:如果app依赖了一个使用 Data Binding 的库,那么app module 的 build.gradle 也必须配置 Data Binding。
## Data Binding 布局文件 - (View)
Data binding 的布局文件与传统布局文件有一点不同。它以一个 layout 标签作为根节点,里面是 data 标签与 view 标签。view 标签的内容就是不使用 Data Binding 时的普通布局文件内容。以下是一个例子:
```xml
```
### 数据对象 - (Model)
假设你有一个 plain-old Java object(POJO) 的 User 对象。
```java
public class User {
private final String mFirstName;
private final String mLastName;
private int mAge;
public User(String firstName, String lastName, int age) {
mFirstName = firstName;
mLastName = lastName;
mAge = age;
}
}
```
或者是 JavaBean 对象:
```java
public class User {
private final String mFirstName;
private final String mLastName;
private int mAge;
public User(String firstName, String lastName, int age) {
mFirstName = firstName;
mLastName = lastName;
mAge = age;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
public int getAge() {
return mAge;
}
}
```
从 Data Binding 的角度看,这两个类是一样的。用于 TextView 的 `android:text` 属性的表达式`@{user.firstName} `,会读取 POJO 对象的 `firstName` 字段以及 JavaBeans 对象的 `getFirstName() `方法。
### 绑定数据 - (ViewModel)
在默认情况下,会基于布局文件生成一个继承于 `ViewDataBinding` 的 Binding 类,将它转换成帕斯卡命名并在名字后面接上`Binding`。例如,布局文件叫 `main_activity.xml `,所以会生成一个 `MainActivityBinding` 类。这个类包含了布局文件中所有的绑定关系,会根据绑定表达式给布局文件赋值。在 inflate 的时候创建 binding 的方法如下:
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ActivityBaseBinding 类是自动生成的
ActivityBaseBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_base);
User user = new User("Connor", "Lin");
// 所有的 set 方法也是根据布局中 variable 名称生成的
binding.setUser(user);
}
```
***
### 事件处理
本部分源码请参考 [DataBindingDemo][DataBindingDemo] -> `EventActivity` 部分。
类似于 android:onClick 可以指定 Activity 中的函数,Data Binding 也允许处理从视图中发送的事件。
有两种实现方式:
+ 方法调用
+ 监听绑定
> 二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。
#### 方法调用
相较于 android:onClick ,它的优势在于表达式会在编译时处理,如果函数不存在或者函数签名不对,编译将会报错。
以下是个例子:
```java
public class EventHandler {
private Context mContext;
public EventHandler(Context context) {
mContext = context;
}
public void onClickFriend(View view) {
Toast.makeText(mContext, "onClickFriend", Toast.LENGTH_LONG).show();
}
}
```
表达式如下:
```xml
```
#### 监听绑定
监听绑定在事件发生时调用,可以使用任意表达式
> 此功能在 Android Gradle Plugin version 2.0 或更新版本上可用.
在方法引用中,方法的参数必须与监听器对象的参数相匹配。在监听绑定中,只要返回值与监听器对象的预期返回值相匹配即可。
以下是个例子:
```java
public void onTaskClick(Task task) {
task.run();
}
```
表达式如下:
```xml
```
当一个回调函数在表达式中使用时,数据绑定会自动为事件创建必要的监听器并注册监听。
##### 关于参数
+ 参数有两种选择:要么不写,要么就要写全。
```xml
或
```
+ lambda 表达式可添加一个或多个参数,同时参数可任意命名
```java
public class EventHandler {
public void onTaskClickWithParams(View view, Task task) {
task.run();
}
}
```
```xml
```
或者
```java
public class EventHandler {
public void onCompletedChanged(Task task, boolean completed) {
if(completed) {
task.run();
}
}
}
```
```xml
```
+ 表达式结果有默认值 null、0、false等等
+ 表达式中可以使用void
```xml
```
##### 关于表达式
+ 复杂的表达式会使布局难以阅读和维护,这种情况我们最好将业务逻辑写到回调函数中
+ 也有一些特殊的点击事件 我们需要使用不同于 android:onClick 的属性来避免冲突。
下面是一些用来避免冲突的属性:
|Class|Listener Setter|Attribute|
|:---:|:---:|:---:|
|SearchView|setOnSearchClickListener(View.OnClickListener)|android:onSearchClick|
|ZoomControls|setOnZoomInClickListener(View.OnClickListener)|android:onZoomIn|
|ZoomControls|setOnZoomOutClickListener(View.OnClickListener)|android:onZoomOut|
***
## 布局详情
本部分源码请参考 [DataBindingDemo][DataBindingDemo] -> `CombineActivity` 部分
### 导入(Imports)
+ data 标签内可以有多个 import 标签。你可以在布局文件中像使用 Java 一样导入引用
```xml
```
+ 当类名发生冲突时,可以使用 alias
```xml
```
+ 导入的类型也可以用于变量的类型引用和表达式中
```xml
```
> 注意:Android Studio 还没有对导入提供自动补全的支持。你的应用还是可以被正常编译,要解决这个问题,你可以在变量定义中使用完整的包名。
+ 导入也可以用于在表达式中使用静态方法
```java
public class MyStringUtils {
public static String capitalize(final String word) {
if (word.length() > 1) {
return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
}
return word;
}
}
```
```xml
…
```
+ java.lang.* 包中的类会被自动导入,可以直接使用,例如, 要定义一个 String 类型的变量
```xml
```
### 变量 Variables
+ data 标签中可以有任意数量的 variable 标签。每个 variable 标签描述了会在 binding 表达式中使用的属性。
```xml
```
+ 可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法。
```xml
```
+ binding 类会生成一个命名为 context 的特殊变量(其实就是 rootView 的 getContext() ) 的返回值),这个变量可用于表达式中。 如果有名为 context 的变量存在,那么生成的这个 context 特殊变量将被覆盖。
```xml
```
```java
public String loadString(Context context) {
// 使用生成的context变量
return context.getResources().getString(R.string.string_from_context);
}
```
### 自定义绑定类名
默认情况下,binding 类的名称取决于布局文件的命名,以大写字母开头,移除下划线,后续字母大写并追加 “Binding” 结尾。这个类会被放置在 databinding 包中。举个例子,布局文件 contact_item.xml 会生成 ContactItemBinding 类。如果 module 包名为 com.example.my.app ,binding 类会被放在 com.example.my.app.databinding 中。
通过修改 data 标签中的 class 属性,可以修改 Binding 类的命名与位置。举个例子:
```xml
...
```
以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 `“.”`:
```xml
...
```
这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中:
```xml
...
```
### Includes
在使用应用命名空间的布局中,变量可以传递到任何 `include` 布局中。
```xml
```
> 需要注意, activity_combine.xml 与 include.xml 中都需要声明 user 变量。
Data binding 不支持直接包含 `merge` 节点。举个例子, 以下的代码**不能正常运行** :
```xml
```
### 表达式语言
#### 通用特性
表达式语言与 Java 表达式有很多相似之处。下面是相同之处:
+ 数学计算 + - / * %
+ 字符串连接 +
+ 逻辑 && ||
+ 二进制 & | ^
+ 一元 + - ! ~
+ 位移 >> >>> <<
+ 比较 == > < >= <=
+ instanceof
+ 组 ()
+ 字面量 - 字符,字符串,数字, null
+ 类型转换
+ 函数调用
+ 字段存取
+ 数组存取 []
+ 三元运算符 ?:
例子:
```xml
```
> 在xml中转义是不可避免的,如 : 使用“&&”是编译不通过的,需要使用转义字符 "&&"
**附:常用的转义字符**
|显示结果|描述|转义字符|十进制|
|:---:|:---:|:---:|:---:|
| |空格| | |
|<|小于号|<|<|
|>|大于号|>|>|
|&|与号|&|&|
|"|引号|"|"|
|'|撇号|' |'|
|×|乘号|×|×|
|÷|除号|÷|÷|
#### 不支持的操作符
一些 Java 中的操作符在表达式语法中不能使用。
+ this
+ super
+ new
+ 显式泛型调用 ``
#### Null合并运算符
Null合并运算符 `??` 会在非 null 的时候选择左边的操作,反之选择右边。
```
android:text="@{user.lastName ?? `Default LastName`}"
```
等同于
```
android:text="@{user.lastName != null ? user.lastName : `Default LastName`}"
```
#### 容器类
通用的容器类:数组,lists,sparse lists,和 maps,可以用 `[]` 操作符来存取
```xml
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
```
#### 字符串常量
使用单引号把属性包起来,就可以很简单地在表达式中使用双引号:
```xml
android:text='@{map["firstName"]}'
```
也可以用双引号将属性包起来。这样的话,字符串常量就可以用 " 或者反引号 ( ` ) 来调用
```xml
android:text="@{map[`firstName`}"
android:text="@{map["firstName"]}"
```
#### 资源
也可以在表达式中使用普通的语法来引用资源:
```
android:text="@{@string/fullname(user.fullName)"
```
字符串格式化和复数形式可以这样实现:
```
android:text="@{@plurals/sample_plurals(num)}"
```
当复数形式有多个参数时,应该这样写:
```
android:text="@{@plurals/numbers(num, num)}"
```
一些资源需要显示类型调用。
|Type|Normal Reference|Expression Reference|
|:---|:---|:---|
|String[]|@array|@stringArray|
|int[]|@array|@intArray|
|TypedArray|@array|@typedArray|
|Animator |@animator |@animator|
|StateListAnimator|@animator |@stateListAnimator|
|color int|@color|@color|
|ColorStateList|@color|@colorStateList|
***
## 数据对象 (Data Objects)
任何 POJO 对象都能用在 Data Binding 中,但是更改 POJO 并不会同步更新 UI。Data Binding 的强大之处就在于它可以让你的数据拥有更新通知的能力。
有三种不同的动态更新数据的机制:
+ Observable 对象
+ Observable 字段
+ Observable 容器类
当以上的 observable 对象绑定在 UI 上,数据发生变化时,UI 就会同步更新。
### Observable 对象
当一个类实现了 Observable 接口时,Data Binding 会设置一个 listener 在绑定的对象上,以便监听对象字段的变动。
Observable 接口有一个添加/移除 listener 的机制,但通知取决于开发者。为了简化开发,Android 原生提供了一个基类 `BaseObservable` 来实现 listener 注册机制。这个类也实现了字段变动的通知,只需要在 getter 上使用 Bindable 注解,并在 setter 中通知更新即可。
```java
public class ObservableContact extends BaseObservable {
private String mName;
private String mPhone;
public ObservableContact(String name, String phone) {
mName = name;
mPhone = phone;
}
@Bindable
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPhone() {
return mPhone;
}
public void setPhone(String phone) {
mPhone = phone;
notifyPropertyChanged(BR.phone);
}
}
```
`BR` 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry。
当数据发生变化时需要调用 `notifyPropertyChanged(BR.firstName)` 通知系统 `BR.firstName` 这个 entry 的数据已经发生变化以更新UI。
### ObservableFields
创建 Observable 类还是需要花费一点时间的,如果想要省时,或者数据类的字段很少的话,可以使用 `ObservableField` 以及它的派生 `ObservableBoolean`、
`ObservableByte` 、`ObservableChar `、`ObservableShort`、`ObservableInt`、`ObservableLong`、`ObservableFloat`、`ObservableDouble`、
`ObservableParcelable` 。
ObservableFields 是包含 observable 对象的单一字段。原始版本避免了在存取过程中做打包/解包操作。要使用它,在数据类中创建一个 public final 字段:
```java
public class ObservableFieldContact {
public ObservableField mName = new ObservableField<>();
public ObservableField mPhone = new ObservableField<>();
public ObservableFieldContact(String name, String phone) {
mName.set(name);
mPhone.set(phone);
}
}
```
要存取数据,只需要使用 get() / set() 方法:
```java
mObservableFieldContact.mName.set("ConnorLin");
mObservableFieldContact.mPhone.set("12345678901");
String name = mObservableFieldContact.mName.get();
```
### Observable Collections 容器类
一些应用会使用更加灵活的结构来保持数据。Observable 容器类允许使用 key 来获取这类数据。当 key 是类似 String 的一类引用类型时,使用 ObservableArrayMap 会非常方便。
```java
ObservableArrayMap mUser = new ObservableArrayMap<>();
mUser.put("firstName", "Connor");
mUser.put("lastName", "Lin");
mUser.put("age", "28");
mBinding.setUser(mUser);
```
在布局中,可以用 String key 来获取 map 中的数据:
```xml
…
```
当 key 是整数类型时,可以使用 ObservableArrayList :
```java
ObservableArrayList user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add("17");
```
在布局文件中,使用下标获取列表数据:
```xml
…
```
***
## 生成绑定
生成的 binding 类将布局中的 View 与变量绑定在一起。就像先前提到过的,类名和包名可以自定义 。生成的 binding 类会继承 ViewDataBinding 。
### Creating
binding 应该在 inflate 之后创建,确保 View 的层次结构不会在绑定前被干扰。绑定布局有好几种方式。最常见的是使用 binding 类中的静态方法。inflate 函数会 inflate View 并将 View 绑定到 binding 类上。此外有更加简单的函数,只需要一个 LayoutInflater 或一个 ViewGroup:
```java
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
```
如果布局使用不同的机制来 inflate,则可以独立做绑定操作:
```java
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
```
有时绑定关系是不能提前确定的。这种情况下,可以使用 DataBindingUtil :
```java
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
```
### Views With IDs
布局中每一个带有 ID 的 View,都会生成一个 public final 字段。binding 过程会做一个简单的赋值,在 binding 类中保存对应 ID 的 View。这种机制相比调用 findViewById 效率更高。举个例子:
```xml
```
将会在 binding 类内生成:
```java
public final TextView firstName;
public final TextView lastName;
```
ID 在 Data Binding 中并不是必需的,但是在某些情况下还是有必要对 View 进行操作。
### Variables
每一个变量会有相应的存取函数:
```xml
```
并在 binding 类中生成对应的 getters 和 setters:
```java
public com.connorlin.databinding.model.User getUser();
public void setUser(com.connorlin.databinding.model.User user);
public Drawable getImage();
public void setImage(Drawable image);
public String getNote();
public void setNote(String note);
```
### ViewStubs
本部分源码请参考 [DataBindingDemo][DataBindingDemo] -> `ViewStubActivity` 部分。
ViewStub 相比普通 View 有一些不同。ViewStub 一开始是不可见的,当它们被设置为可见,或者调用 inflate 方法时,ViewStub 会被替换成另外一个布局。
因为 ViewStub 实际上不存在于 View 结构中,binding 类中的类也得移除掉,以便系统回收。因为 binding 类中的 View 都是 final 的,所以Android 提供了一个叫 `ViewStubProxy` 的类来代替 ViewStub 。开发者可以使用它来操作 ViewStub,获取 ViewStub inflate 时得到的视图。
但 inflate 一个新的布局时,必须为新的布局创建一个 binding。因此, `ViewStubProxy` 必须监听 ViewStub 的 `ViewStub.OnInflateListener`,并及时建立 binding。由于 ViewStub 只能有一个 `OnInflateListener`,你可以将你自己的 listener 设置在 ViewStubProxy 上,在 binding 建立之后, listener 就会被触发。
```xml
```
在 Java 代码中获取 binding 实例,为 `ViewStubProy` 注册 `ViewStub.OnInflateListener` 事件:
```java
mActivityViewStubBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
mActivityViewStubBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
IncludeBinding viewStubBinding = DataBindingUtil.bind(inflated);
User user = new User("Connor", "Lin", 28);
viewStubBinding.setUser(user);
}
});
```
通过 `ViewStubProxy` 来 inflate ViewStub :
```java
public void inflate(View view) {
if (!mActivityViewStubBinding.viewStub.isInflated()) {
mActivityViewStubBinding.viewStub.getViewStub().inflate();
}
}
```
> 此处 `isInflated()` 和 `getViewStub()` 会标红,请不要担心,这并不是错误,是 [ViewStubProxy](https://developer.android.com/reference/android/databinding/ViewStubProxy.html) 中的方法。
### 高级绑定
#### 动态变量
有时候,有一些不可知的 binding 类。例如,RecyclerView.Adapter 可以用来处理不同布局,这样的话它就不知道应该使用哪一个 binding 类。而在 onBindViewHolder(VH, int) ) 的时候,binding 类必须被赋值。
在这种情况下,RecyclerView 的布局内置了一个 item 变量。 BindingHolder 有一个 getBinding 方法,返回一个 ViewDataBinding 基类。
```java
public void onBindViewHolder(BindingHolder holder, int position) {
holder.getBinding().setVariable(BR.item, mItemList.get(position));
holder.getBinding().executePendingBindings();
}
```
以上,详细请参考 [DataBindingDemo][DataBindingDemo] -> `MainActivity` 部分(使用 `RecyclerView` 实现)。
#### 直接 binding
当变量或者 observable 发生变动时,会在下一帧触发 binding。有时候 binding 需要马上执行,这时候可以使用 `executePendingBindings()`。
#### 后台线程
只要数据不是容器类,你可以直接在后台线程做数据变动。Data binding 会将变量/字段转为局部量,避免同步问题。
***
## 属性设置
本部分源码请参考 [DataBindingDemo][DataBindingDemo] -> `AttributeSettersActivity` 部分。
当绑定数据发生变动时,生成的 binding 类必须根据 binding 表达式调用 View 的 setter 函数。Data binding 框架内置了几种自定义赋值的方法。
### 自动设置属性
对一个 attribute 来说,Data Binding 会尝试寻找对应的 setAttribute 函数。属性的命名空间不会对这个过程产生影响,只有属性的命名才是决定因素。
举个例子,针对一个与 TextView 的 android:text 绑定的表达式,Data Binding会自动寻找 setText(String) 函数。如果表达式返回值为 int 类型, Data Binding则会寻找 setText(int) 函数。所以需要小心处理函数的返回值类型,必要的时候使用强制类型转换。
需要注意的是,Data Binding 在对应名称的属性不存在的时候也能继续工作。你可以轻而易举地使用 Data Binding 为任何 setter “创建” 属性。
如 [DataBindingDemo][DataBindingDemo] 中的自定义布局 Card,并没有添加 `declare-styleable`,但是可以使用自动 setter 的特性来调用这些函数。
```xml
```
### 重命名属性设置
一些属性的命名与 setter 不对应。针对这些函数,可以用 BindingMethods 注解来将属性与 setter 绑定在一起。举个例子, `android:tint` 属性可以这样与 `setImageTintList(ColorStateList) )` 绑定,而不是 setTint :
```java
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
```
Android 框架中的 setter 重命名已经在库中实现了,我们只需要专注于自己的 setter。
### 自定义属性设置
一些属性需要自定义 setter 逻辑。例如,目前没有与 `android:paddingLeft` 对应的 setter,只有一个 `setPadding(left, top, right, bottom)` 函数。结合静态 binding adapter 函数与 BindingAdapter 注解可以让开发者自定义属性 setter。
Android 属性已经内置一些 BindingAdapter。例如,这是一个 paddingLeft 的自定义 setter:
```java
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
```
Binding adapter 在其他自定义类型上也很好用。举个例子,一个 loader 可以在非主线程加载图片。
当存在冲突时,开发者创建的 binding adapter 会覆盖 Data Binding 的默认 adapter。
你也可以创建多个参数的 adapter:
```java
// 无需手动调用此函数
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Glide.with(view.getContext()).load(url).error(error).into(view);
}
```
```xml
```
当 imageUrl 与 error 存在时这个 adapter 会被调用。imageUrl 是一个 String,error 是一个 Drawable。
- 在匹配时自定义命名空间会被忽略
- 你可以为 android 命名空间编写 adapter
Binding adapter 方法可以获取旧的赋值。只需要将旧值放置在前,新值放置在后:
```java
@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());
}
}
```
事件 handlers 仅可用于只拥有一个抽象方法的接口或者抽象类。例如:
```java
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
```
当 listener 内置多个函数时,必须分割成多个 listener。例如, View.OnAttachStateChangeListener 内置两个函数: `onViewAttachedToWindow()`与 `onViewDetachedFromWindow()` 。在这里必须为两个不同的属性创建不同的接口。
```java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
```
因为改变一个 listener 会影响到另外一个,我们必须编写三个不同的 adapter,包括修改一个属性的和修改两个属性的。
```java
@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` `View.OnAttachStateChangeListener` 而不是 `set`。 `android.databinding.adapters.ListenerUtil`可以用来辅助跟踪旧的 listener 并移除它。
对应 `addOnAttachStateChangeListener(View.OnAttachStateChangeListener) ) `支持的 api 版本,
通过向 `OnViewDetachedFromWindow` 和 `OnViewAttachedToWindow` 添加 `@TargetApi(VERSION_CODES.HONEYCHOMB_MR1)` 注解,
Data Binding 代码生成器会知道这些 listener 只会在 Honeycomb MR1 或更新的设备上使用。
***
## 转换器Converters
### 对象转换
当 binding 表达式返回对象时,会选择一个 setter(自动 Setter,重命名 Setter,自定义 Setter),将返回对象强制转换成 setter 需要的类型。
下面是一个使用 ObservableMap 保存数据的例子:
```xml
```
在这里, userMap 会返回 Object 类型的值,而返回值会被自动转换成 setText(CharSequence) 需要的类型。当对参数类型存在疑惑时,开发者需要手动做类型转换。
### 自定义转换
有时候会自动在特定类型直接做类型转换。例如,当设置背景的时候:
```xml
```
在这里,背景需要的是 Drawable ,但是 color 是一个整数。当需要 Drawable 却返回了一个整数时, int 会自动转换成 ColorDrawable 。这个转换是在一个 BindingConversation 注解的静态函数中实现:
```java
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
```
需要注意的是,这个转换只能在 setter 阶段生效,所以 不允许 混合类型:
```xml
```
***
## Android Studio对Data Binding的支持
* Android Studio 支持 Data Binding 表现为:
* 语法高亮
* 标记表达式语法错误
* XML 代码补全
* 跳转到声明或快速文档
> 注意:数组和泛型类型,如 Observable 类,当没有错误时可能会显示错误。
* 在预览窗口可显示 Data Binding 表达式的默认值。例如:
```xml
```
如果你需要在设计阶段显示默认值,你可以使用 `tools` 属性代替默认值表达式,详见 [设计阶段布局属性 ](http://tools.android.com/tips/layout-designtime-attributes)
[DataBindingDemo]: https://github.com/ConnorLin/DataBindingDemo
## 参考资料
1. [Data Binding Library](https://developer.android.com/topic/libraries/data-binding/index.html)
2. [安卓 Data Binding 使用方法总结](http://blog.csdn.net/zhaizu/article/details/51491455)
3. [(译)Data Binding 指南](http://yanghui.name/blog/2016/02/17/data-binding-guide/?utm_source=tuicool&utm_medium=referral)
4. [精通 Android Data Binding](https://github.com/LyndonChin/MasteringAndroidDataBinding/blob/master/README.md)
***
**我的简书账号是 ConnorLin,欢迎关注!**
**我的简书专题是 Android开发技术分享,欢迎关注!**
**[我的个人博客](http://connorlin.github.io/) 欢迎关注!**
**原创文章,欢迎转载,转载请注明出处!**

**欢迎您扫一扫上面的微信公众号,订阅我的博客!**