参考:
官方文档
Data Binding Guide(中文版 - Data Binding(数据绑定)用户指南)
基于中文版的另一版教程
整体概述:
1.环境
2.简单使用
-- 1).布局文件定义
-- 2).Data数据
-- 3).Binding数据
3.深入Layout
-- 1)导入import
-- 2) Variables详解
-- 3)自定义Binding类名
-- 4)includes使用
-- 5)常用表达式
4.Data对象
-- 1)Observable对象
-- 1)Observable字段
-- 1)Observable集合
5.Binding生成
-- 1) 带 ID 的 View
-- 2)ViewStubs
-- 3) Dynamic Variables(RecycleView使用)
6 属性Setters
7.转换器 (Converters)
8.绑定事件处理方法
9.BindingAdapter的使用场景
10.双向绑定
1.环境
Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本。
android {
....
dataBinding {
enabled = true
}
}
2.简单使用
1).布局文件定义
<?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>
user变量属性 type为引用的Data对象或者其他对象
<data>
<variable name="user" type="com.example.User"/>
</data>
属性引用
@{user.firstName}
@{user.lastName}
2)Data数据
Pojo对象
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;
}
}
注意:
Pojo对象特点:一旦拥有数据,不会改变
JavaBeans对象:可以自定义数据输出,在DataBinding中,实际获取的值是get属性方法,如果不使用自定义get方法,默认Pojo和JavaBeans对象是一样的。(不建议同时使用,否则会出现数据不一致的情况)
3)Binding数据
默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”
后缀。上述的layout文件是main_activity.xml
,因此生成的类名是MainActivityBinding
。此类包含从layout属性到layout的Views中所有的bindings(例如user变量),并且它还知道如何给Binding表达式分配数值。创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.直接使用绑定并setContentView
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
如想获取View对象,可使用
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
//2.ListView或者RecyclerView adapter使用Data Binding时
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
如想获取View对象,可使用
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
User user = new User("Test", "User");
binding.setUser(user);
}
建议分开绑定,当使用不同的机制载入layout时,使用下面方式:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
3.深入Layout
1)导入import
注意:java.lang.* 包中的类会被自动导入,可直接使用
<data>
<import type="android.view.View"/>
</data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当类名有冲突时,可以让其中一个加一个别名来使用:
<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
使用方法建议:
1.当只是使用类Static属性和方法,不需要额外再设置属性,可直接使用以下方式
<import type="com.example.real.estate.View" alias="Vista"/>
2.当需要使用类所有属性和方法,并需要设置属性,例如model数据,使用以下方式。
<variable
name="pageNew"
type="com.gson.HomePageNew"/>
或者
<import type="com.gson.HomePageNew">
<variable name="pageNew"
type="HomePageNew"/>
2) Variables详解:
在data中可以使用任意数量的variable元素。每一个variable元素描述了一个用于layout文件中Binding表达式的属性。
<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>
注意:
- 1.Variables类型在编译时被检查,如果一个变量实现了可观察的或可观察到的集合,就应该在类型中反映粗来,如果这个变量是一个
类/接口
,但没有实现
可观察的接口eg:BaseObservable
,那么该变量不会被观察到。 - 2.当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。
- 3.每一个Binding类中属性,都会有一个默认的属性eg:null(引用) 0(int)false(boolean) ,直到setter调用。
3)自定义Binding类名
一般直接使用layout对应的名称
<data class="ContactItem">
...
</data>
4)includes
通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:
1.上一层Layout
<?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>
2.下一层layout
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="android.view.View"></import>
<variable
name="user"
type="com.example.User"></variable>
</data>
。。。。
</layout>
注意:在name.xml
以及contact.xml
两个layout文件中必需要有user variable
5)常用表达式
数学 + - / * %
字符串连接 +
逻辑 && ||
二进制 & | ^
一元运算 + - ! ~
移位 >> >>> <<
比较 == > < >= <=
instanceof
分组 ()
null
Cast
方法调用
数据访问 []
三元运算 ?:
eg:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
- 不支持属性:
this
super
new
显式泛型调用
- Null合并操作
android:text="@{user.displayName ?? user.lastName}"
||等价
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
- 属性引用
android:text="@{user.lastName}"
- 集合使用
<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"]}'
双引号包含属性(缺点:不能使用 ”app“ + ”name“ 的形式拼接
android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
- Resources的使用:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
- 格式化字符串
android:text="@{@string/nameFormat(firstName, lastName)}"
<string name="nameFormat">%s, %s</string>
当复数需要多个参数时
Strings类
<plurals name="orange">
<item quantity="one">Have an orange</item>
<item quantity="other">Have %d oranges</item>
</plurals>
xml:
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
- 一些资源需要明确类型调用。
Type【类型】 | Normal Reference【正常引用】 | Expression Reference【表达式引用】 |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
colorint | @color | @color |
ColorStateList | @color | @colorStateList |
4.Data对象----->ObservableActivity.java
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:
Observable对象
、ObservableFields
以及observable collections
。
当这些可观察Data对象绑定到UI,Data对象属性的更改后,UI也将自动更新。
1) Observable对象:
实现android.databinding.Observable
接口的类可以允许附加一个监听器到Bind
对象以便监听对象上的所有属性的变化。
Observable
接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable
的基类为实现监听器注册机制而创建。Data
实现类依然负责通知当属性改变时。这是通过指定一个Bindable
注解给getter
以及setter
内通知来完成的。(注意在get方法上加上@Bindable注释,同时记得给xml中对应的binding赋值,eg: binding.setResult(scanResult);
)
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
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
类文件中生成一个Entry
。BR
类文件会在模块包内生成。如果用于Data
类的基类不能改变,Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器。
2) Observable 字段
一些小工作会涉及到创建Observable
类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields
。ObservableFields
是自包含具有单个字段的observable
对象。它有所有基本类型和一个是引用类型。要使用它需要在data
对象中创建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();
}
使用方法:
user.firstName.set("Google");
int age = user.age.get();
3)Observable 集合
系统为我们提供了所有的 primitive type
所对应的Observable类
,eg:ObservableInt
、ObservableFloat
、ObservableBoolean
及ObservableField
对应着 reference type
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>
…
<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);
在layout文件中,通过索引可以访问list:
<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生成
1) 带 ID 的 View
Data Binding
有效降低了代码的冗余性,甚至完全没有必要再去获取一个 View
实例,但是情况不是绝对的,万一我们真的就需要了呢?不用担心,只要给View
定义一个 ID,Data Binding
就会为我们生成一个对应的 final
变量。
<TextView
android:id="@+id/firstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
上面代码中定义了一个 ID 为 firstName
的 TextView
,那么它对应的变量就是
public final TextView firstName;
2)ViewStubs -----> ViewStubActivity.java
ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。
由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。
当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy必需监听ViewStub的OnInflateListener监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy允许开发者在其上设置一个OnInflateListener它会在建立Binding后调用。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
...>
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
... />
</LinearLayout>
</layout>
//必须在设置监听后去设置binding
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
3) Dynamic Variables---->DynamicActivity.class
以 RecyclerView 为例,Adapter 的 DataBinding 需要动态生成,因此我们可以在 onCreateViewHolder 的时候创建这个 DataBinding,然后在 onBindViewHolder 中获取这个 DataBinding。
public static class BindingHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public BindingHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.getContext()),
R.layout.list_item,
viewGroup,
false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
注意此处 DataBindingUtil 的用法:
ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.getContext()),
R.layout.list_item,
viewGroup,
false);
6 属性Setters
有了Data Binding,即使属性没有在declare-styleable
中定义,我们也可以通过 xml 进行赋值操作。 为了演示这个功能,自定义 View -NameCard,属性资源R.styleable.NameCard中只定义了一个age
属性,其中firstName
和lastName
只有对应的两个setter
方法。
只要有setter
方法就可以像下面代码一样赋值:
<com.liangfeizc.databindingsamples.attributesetters.UserView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/largePadding"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"
app:age="27" />
7.转换器 (Converters) ----->ConversionsActivity.java
使用Converter一定要保证它不会影响到其他的属性,例如这个@BindingConversion
-convertColorToString就会影响到android:visibility, 因为他们都是都符合从 int 到 int 的转换。
在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过DataBinding可以进行转换。
例如,下面代码中如果要为属性android:background赋值一个int型的 color 变量:
<View
android:background="@{isError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
只需要定义一个标记了 @BindingConversion 的静态方法即可(方法的定义位置可以随意):
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
8.绑定事件处理方法
9. BindingAdapter的使用场景
10.双向绑定
用法举例
很简单,在要使用双向绑定的地方,使用 “@={}” 即可。
<EditText android:text="@={user.firstName}" />
firstName 必须是 ObservableField <T> 类型
适用范围
双向绑定只适用于那些某个属性绑定监听事件的控件,如
- TextView/EditView/Button (android:text, TextWatcher)
- CheckBox (android:checked, OnCheckedChangeListener)
- DatePicker(android:year, android:month, android:day, OnDateChangedListener)
- TimePicker(android:hour, android:minute, OnTimeChangedListener)
- RatingBar(android:rating, OnRatingBarChangeListener)
…