参考:
https://developer.android.google.cn/topic/libraries/data-binding/index.html
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html
需要 Android 2.1(API 7)以及 gradle 1.5.0-alpha1版本以上。
在 app.gradle 中添加配置:
android { ... dataBinding { enabled = true } }Data Binding 的布局文件需要在你的布局文件外嵌入 layout 标签,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> <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 标签中的 variable 表示这个布局中用到的属性。type:表示使用的属性所在的类的全类名,name:可以自定义。
随后,在 TextView 的 text 属性中,使用语法为”@{}”的语句绑定与之相关的属性值。
构建一个用户数据类 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; } }或者:
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }上述步骤完成后,在项目 build/intermediates/classes/debug/com/example/databinding/ 目录下可以查看生成的相关类,生成的类名规则根据你的布局文件生成,如布局文件名为:activity_main.xml,生成的 Binding 类名为:ActivityMainBinding。且需要在布局对应的 Activity 的 onCreate 中设置绑定:
@Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("张三", "hello world"); binding.setUser(user); }至此,你可以运行项目,两个 TextView 中会显示 User 对象属性对应的值。
也可以通过获取 binding 对象:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());如果在 ListView 或者 RecyclerView adapter 中绑定 items,可以使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); //or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);Data Binding 允许 view 的事件绑定。除了少数事件以外,事件属性名字由 listener 名字支配。如:onLongClickListener,则使用 android:onLongClick 为控件添加长点击事件。
处理事件有两种方式:
方法引用(Meathod References):方法名必须和对应的 listener 事件方法名匹配,如果不匹配,不设置事件监听。Data Binding 封装了方法引用和对应数据对象在一个 listener,并且为目标 view 添加了这个事件监听。
监听绑定(Listener Bindings):当事件触发的时候,这里使用 的是 lambda 表达式。Data Binding 总会给目标 view 创建一个事件监听器,当事件调用的时候,监听器就会计算 lambda 表达式。
方法引用和监听绑定之间的主要区别就是实际的监听的实现在数据绑定的时候,而不是事件触发的时候。如果更喜欢当事件触发的时候计算表达式,应该使用监听绑定。要将事件交由其处理程序,使用正常的绑定表达式,其值为要调用的方法名称。如:
public class MyHandlers { public void onClickFriend(View view) { ... } }在对应的 xml 中:
<?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="@{handlers::onClickFriend}"/> </LinearLayout> </layout> 注意:表达式中的方法名必须与 listener 对象中的方法名完全匹配。监听绑定是当事件触发时,绑定表达式运行。类似于方法引用,但是监听绑定能够运行任意数据绑定表达式。这个功能需要在 gradle 版本2.0以上。
在方法引用中,方法参数必须匹配事件监听器的参数。在监听绑定中,只需要与事件监听方法返回参数匹配即可。例如:
public class Presenter { public void onSaveClick(Task task){} }此时,绑定点击事件:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/ apk/res/android"> <data> <variable name="task" type="com.android.example.Task" /> <variable name="presenter" type="com.android.example.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout>监听器通过 lambda 表达式表示,且 lambda 表达式只能作为表达式的根元素。当表达式使用回调时,Data Binding 自动创建必要的 listener 和注册。
表达式中,可以忽略所有的参数和名称。如果想要使用参数名,也可以使用它们。例如:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"或者,如果想要在表达式中使用参数,例如:
public class Presenter { public void onSaveClick(View view, Task task){} } android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"也可以使用一个 lambda 表达式包含多个参数:
public class Presenter { public void onCompletedChanged(Task task, boolean completed){} } <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />如果表达式因为 null 对象不能计算,Data Binding 返回默认的 Java 类型值。如:int 为 0,boolean 为 false,等。
如果需要使用三目运算,能够使用 void 作为符号:
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"存在一些特定的点击事件处理程序,他们需要一个除了 android:onClick 之外的属性去避免冲突(没看明白。。。)。以下的属性被创建去避免这些冲突:
可以在 data 元素中使用多个 import 元素。这样方便在布局文件中引用类。如:
<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}"/>当类名冲突的时候,其中一个类名可以通过 alias 重命名:
<import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/>导入的类型在变量和表达式中能够作为一个类型引用:
<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data>在引用表达式中的静态字段和方法时也可以使用导入的类型:
<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"/>在 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>如果 variable 实现了 Observable 或者 Observable collection,应该反映在类型中 。
默认情况下,Binding 类基于 layout 文件生成。
可以通过调整 data 元素的 class 属性将绑定类重命名或放置在不同的包中。如:
<data class="ContactItem"> ... </data>在 module 的 databinding 包中生成 ContactItem 的 binding 类。如果要类生成在一个不同的包,可以使用前缀”.”,如:
<data class=".ContactItem"> ... </data>在 module 包中直接生成了 ContactItem。任何包都可以使用完整的包名提供:
<data class="com.example.ContactItem"> ... </data>变量能够传递到 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> <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>Data Binding 不支持使用一个 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>和 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 ?:
如:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'空合并运算符(??)选择左操作数,如果它不为空,或者如果为空,则选择右操作数。如:
android:text="@{user.displayName ?? user.lastName}"等价于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"生成的 data binding 代码自动地检查空指针且帮助避免空指针异常。
常用集合: arrays, lists, sparse lists, and 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"]}'当使用双引号包裹属性值时,字符串应该使用”或者“:
android:text="@{map[`firstName`}" android:text="@{map['firstName']}"使用正常语法访问资源:
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)}"一些资源需要显示类型:
可通过 Observable objects,observable field,observable collections三种机制及时通知数据变化。使用其中的一种方式绑定到 UI,数据对象属性变化时,对应的 UI 将会自动更新。
类实现 Observable 接口,binding 会监听这个类所有属性所发生的变化。
Observable 接口具有添加和移除 listener 机制。可以通过使用一个 BaseObservable 实现 Observable 接口去实现 listener 注册机制。它是通过一个 Bindable 注释指定给 getter 和在 setter 中 notify。
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); } }编译期间,Bindable 注释会在 BR 类文件中生成一个条目。BR 文件在 module 包下。如果数据类的基类不能更改,可以使用实现了 Observable 接口的 PropertyChangeRegistry 去有效的存储和通知 listener 。
ObservableField 是单个字段且自包含 Observable 对象。创建 Observable 类时,可以使用 ObservableField 及类似的 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。要使用它,创建一个 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();很多应用使用动态结构去持有 data。Observable Collections 使用 key 去访问这些数据对象。当键(key)为引用类型(如:String)时,可以使用 ObservableArrayMap :
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);布局文件中,map 的访问通过 key :
<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"/>当键(key)是一个整形数值时,ObservableArrayList 则非常实用:
ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);在对应的布局中,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"/>binding 类将布局变量和布局中的视图链接。生成的 Binding 类可以自定义名称和包名并继承自 ViewDataBinding。
可通过 inflate 方法加载视图并绑定:
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);在布局中为每个 view 使用 id 将生成一个 public final 字段。binding 在视图层次结构单独传递,并通过 id 获取 view。这种机制,比调用 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;id 几乎没有必要,但还是会有一些情况需要访问 view。
每一个变量要指定类。
<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 类中,将会生成 setter 和 getter 方法:
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);参考:https://developer.android.google.cn/reference/android/view/ViewStub.html
xml 中的 ViewStub 经过 binding 之后会转换成 ViewStubProxy。如:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout ...> <ViewStub android:id="@+id/view_stub" android:layout="@layout/view_stub" ... /> </LinearLayout> </layout>java 代码中,获取 binding 实例,并为 ViewStubProxy 注册 ViewStub.OnInflateListener 事件:
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); } });有时,并不知道具体的绑定类。例如: RecyclerView.Adapter 对任意布局操作,无法得知具体的绑定类。但是,仍然需要在 onBindViewHolder(VH, int) 的时候指定 binding 值:
public void onBindViewHolder(BindingHolder holder, int position) { final T item = mItems.get(position); holder.getBinding().setVariable(BR.item, item); holder.getBinding().executePendingBindings(); }其中,RecyclerView 绑定的 layout 具有一个 “item” 变量。BindingHolder 有一个 getBinding 方法返回 ViewDataBinding 对象。
可以使用 executePendingBindings() 方法。
只要不是集合,可以在后台线程中改变数据模型(data model)。
即使属性没有在 declare-styleable 中定义,我们也可以通过 xml 进行赋值操作。
<com.example.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" />使用 ObservableMaps 保持数据的简例:
<TextView android:text='@{userMap["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>userMap 返回一个对象,并且这个对象会被自动地转换成 setText(CharSequence) 中的参数类型。
有时,在特定的类型中转换应该是自动的。如,设置背景:
<View android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>这里,background 获取一个 Drawable。但是 color 是 integer。int 需要转换为 ColorDrawable。这个转换通过使用 BindingConversion 注释的静态方法完成:
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }注意,如下的混合类型不能转换:
<View android:background="@{isError ? @drawable/error : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>至此,DataBinding 的介绍完成。