Android DataBinding 详解
前几天小试牛刀写了一篇 Android DataBinding 初探,只是简单的介绍了一下 DataBinding 的几个小问题,并没有特别详细的去介绍 DataBinding 的更多方法,这几天看了一下 DataBinding 的官网的相关内容,觉得有必要把官网的用法记录一下,用来参考及以后使用时的参考,以前大家很多人都使用过注解框架,包括 Jake Wharton 大神的 ButterKnife,但 DataBinding 出来后,相信会对此类的框架形成碾压,毕竟是 Google 的官方出品,接下来我们切入正题了,开始详细的去介绍这个 Android DataBinding Library,先上一张概况图:
一、构建环境(Build Environment)
- 要使用 DataBinding 数据库,先从 Android SDK Manager 的支持库里下载该库
- 配置你的应用程序使用数据绑定,在应用程序模块,你的 build.gradle 文件添加数据绑定元素
- 另外,需要注意你使用的 Android Studio 的兼容版本,需要 1.3 及以上的版本
- android {
- ….
- dataBinding {
- enabled = true
- }
- }
android {
....
dataBinding {
enabled = true
}
}
1) DataBinding 表达式
数据绑定的布局文件和我们以前经常写的布局文件稍有不同,并从布局的根标记开始,后面依次是数据元素和视图根元素,即根布局是 layout,接下来是 data 节点,variable 节点,示例如下:
- <?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>
<?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>
数据中的用户变量描述此布局中可能使用的属性:
- <variable name=“user” type=“com.example.User”/>
<variable name="user" type="com.example.User"/>
布局中的表达式使用 “@{}” 语法在属性中写入,在这里,TextView 的文本设置为用户 FirstName 属性:- <span style=“color:#666666;”><TextView android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”
- android:text=“@{user.firstName}”/></span>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
2)数据对象(Data Object)假设你现在有一个普通的 Java 对象(User):
- public class User {
- public final String firstName;
- public final String lastName;
- public User(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
- }
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;
- }
- }
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;
}
}
从数据绑定的角度来看,这两个类是等价的,用于 TextView 中的 android:text 属性的表达式 @{user.firstName} 将访问前者 User 对象中的 firstName 和后者 JavaBeans 对象中的 getFirstName 方法
3)数据绑定(Binding Data)
默认情况下,绑定类将根据 layout 文件的名称生成,首字母大写的命名规范,并添加 “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);
- }
@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);
}
就这样,运行应用程序,你将会在 UI 中看到 Test User,或者你可以通过如下获取 View:- MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你是在 ListView 或者 RecyclerView adapter 中使用 Data Binding 时,你可能使用:
- ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
- //or
- ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
4)事件处理(Event Handing)
数据绑定允许你编写表达式处理,如 onClick 事件,有少数例外事件属性的名称受监听方法名称的约束,例如View.OnLongClickListener 有一个 onLongClick 方法,所以此事件的属性是:Android:onLongClick,它有两种处理事件的方法:
- 方法引用:在表达式中,可以引用符合监听方法签名的方法,当表达式计算为方法引用时,数据绑定将监听器中的方法引用和所有者对象封装在一起,并在目标视图上设置监听器,如果表达式计算为 null,则数据绑定不会创建监听器,而是设置空监听器
- 监听器绑定:这些是在事件发生时计算 Lambda 表达式,数据绑定总是创建一个监听器,它设置在视图上,当事件被发送时,监听器计算 Lambda 表达式
方法引用:事件可以直接绑定到处理程序的方法,类似于 Android:onClick,相比来看 View#onClick 属性更重要的优势是,表达式在编译的时候处理的,所以如果方法不存在或签名是不正确的,你将会在编译时出错,方法引用和监听器绑定之间的主要区别是,当数据绑定时,实际的监听器实现将创建,而不是在触发时,如果你希望事件发生时对表达式进行审核,则应该使用监听器绑定,若将事件分配给其处理程序,请使用常规绑定表达式,该值是要调用的方法名,例如,如果你的数据对象有两种方法:
- public class MyHandlers {
- public void onClickFriend(View view) { … }
- }
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
绑定表达式可以为视图分配单击监听器:
注意表达式中的方法的签名必须与监听对象中的方法的签名完全匹配
- <?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>
<?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>
- public class Presenter {
- public void onSaveClick(Task task){}
- }
public class Presenter {
public void onSaveClick(Task task){}
}
然后你可以在你的 xml 文件中将点击事件绑定如下:
- <?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>
<?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 表达式表示,这些表达式只允许作为表达式的根元素,当表达中使用回调时,数据绑定会自动创建时间的必要监听器和寄存器,当视图触发事件时,数据绑定将审核给定的表达式,与常规绑定方式一样,在审核这些监听器表达式时,任然可以得到数据绑定的
null 和线程安全性
请注意,在上面的例子中,我们还没有定义 View 的参数,通过 onClick(Android.view.View)Listener 绑定为监听器参数提供两种选择:你可以忽略所有方法参数或命名所有参数,如果你更喜欢命名参数,则也可以在表达式中使用它们,例如,上面的表达式可以写成:
- android:onClick=“@{(view) -> presenter.onSaveClick(task)}”
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者如果你想使用表达式中的参数,它可以写成如下:
- public class Presenter {
- public void onSaveClick(View view, Task task){}
- }
public class Presenter {
public void onSaveClick(View view, Task task){}
}
- android:onClick=“@{(theView) -> presenter.onSaveClick(theView, task)}”
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
可以使用多个参数的 Lambda 表达式:
- public class Presenter {
- public void onCompletedChanged(Task task, boolean completed){}
- }
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)}” />
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
如果监听的事件返回类型无效的值,则表达式必须返回相同类型的值。例如,如果要监听长点击事件,则表达式应返回布尔值:
- public class Presenter {
- public boolean onLongClick(View view, Task task){}
- }
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
- android:onLongClick=“@{(theView) -> presenter.onLongClick(theView, task)}”
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
你还可以使用三元表达式:
- android:onClick=“@{(v) -> v.isVisible() ? doSomething() : void}”
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免复杂的监听:监听器表达式是非常强大的,可以使你的代码很容易阅读。另一方面,含有复杂的表达式的监听让你的布局难以阅读和维护。这些表达式应该是简单的,从用户界面传递可用数据到回调方法。您可以在从监听器表达式调用的回调方法中实现任何业务逻辑
三、布局细节(Layout Details)
1)imports
可以在数据元素内使用零个或多个导入元素。这些可以参考在你的布局文件的类,就像在 java:
- <data>
- <import type=“android.view.View”/>
- </data>
<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}”/>
<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”/>
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
现在,Vista 可以用来参考的 com.example.real.estate.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>
<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 还不能很好的兼容支持,变量可能不能在 IDE 中完成自动提示功能。但是你的应用程序将仍然可以编译,你可以通过使用完全限定名称来定义变量解决 IDE 的问题:
- <TextView
- android:text=“@{((User)(user.connection)).lastName}”
- android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”/>
<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”/>
<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 * 自动导入
2)Variables
可以在数据元素内使用任意数量的变量元素。每个变量元素描述可以在布局文件中用于绑定表达式中的布局的属性:
- <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>
<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 的集合,则该类型应反映在类型中。如果变量是不执行 Observable* 接口的基类或接口,则不会观察变量,当有不同的布局文件的各种配置(如头像),变量将被合并。这些布局文件之间不能有冲突的变量定义生成一个名为上下文的特殊变量,用于在需要时绑定表达式。上下文的值是根目录的 getcontext(),上下文变量将被一个显式变量声明所覆盖
3)自定义绑定类名(Custom Binding Class Names)
默认情况下,绑定类是基于布局的文件名生成,开始用大写,去掉下划线 “_”,然后加后缀 “Binding”,这个类将会被放置在一个绑定包的模块包下,例如,布局文件是 contant_item.xml 将生成 ContactItemBinding,如果模块封装为 com.example.my.app,那么它将被放置在 com.example.my.app.databinding
通过调整数据元素的类属性可以将绑定类重命名或放置在不同的包中。例如:
- <data class=“ContactItem”>
- …
- </data>
<data class="ContactItem">
...
</data>
这个生成绑定类中的模块封装在数据绑定包 ContactItem,如果类产生在不同的包中的模块封装内,它可能会加 “.”,如下:
- <data class=“.ContactItem”>
- …
- </data>
<data class=".ContactItem">
...
</data>
在这种情况下,ContactItem 直接在模块封装生成,如果提供完整的包,任何包可以使用:- <data class=“com.example.ContactItem”>
- …
- </data>
<data class="com.example.ContactItem">
...
</data>
4)Includes
通过使用应用程序命名空间和属性中的变量名,可以将变量从包含布局中传递到包含布局中:
- <?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>
<?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 布局文件的用户变量
数据绑定不支持包括合并元素的直接子项。例如,不支持下列布局:
- <?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>
<?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>
5)表达式(Expression Language)
常用表达式给 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="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
this
super
new
- Explicit generic invocation
- android:text=“@{user.displayName ?? user.lastName}”
android:text="@{user.displayName ?? user.lastName}"
在功能上和如下相同:- android:text=“@{user.displayName != null ? user.displayName : user.lastName}”
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用:
- android:text=“@{user.lastName}”
android:text="@{user.lastName}"
避免空指针(Avoiding
NullPointerException)
- <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]}”
<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:text=“@{map[‘firstName’]}”
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
资源(Resources):
- android:padding=“@{large? @dimen/largePadding : @dimen/smallPadding}”
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和复数可通过提供参数判断:
- android:text=“@{@string/nameFormat(firstName, lastName)}”
- android:text=“@{@plurals/banana(bananaCount)}”
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
- Have an orange
- Have %d oranges
- android:text=“@{@plurals/orange(orangeCount, orangeCount)}”
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
- 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);
- }
- }
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);
}
}
- private static class User {
- public final ObservableField<String> firstName =
- new ObservableField<>();
- public final ObservableField<String> lastName =
- new ObservableField<>();
- public final ObservableInt age = new ObservableInt();
- }
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();
user.firstName.set("Google");
int age = user.age.get();
- ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
- user.put(”firstName”, “Google”);
- user.put(”lastName”, “Inc.”);
- user.put(”age”, 17);
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
- <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”/>
<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);
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”/>
<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 生成(Generated Binding)
- MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
- MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果 layout 使用不同的机制
inflate,则可以单独绑定:- MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时 Binding 不能事先知道,在这种情况下,可以使用 DataBindingUtil 类来创建 Binding:
- ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
- parent, attachToParent);
- ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
- <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>
<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;
public final TextView firstName;
public final TextView lastName;
IDS 几乎没有必要在 DataBinding,但仍然有一些情况下,访问 Views 仍然是必要的代码
- <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>
<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);
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);
- public void onBindViewHolder(BindingHolder holder, int position) {
- final T item = mItems.get(position);
- holder.getBinding().setVariable(BR.item, item);
- holder.getBinding().executePendingBindings();
- }
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
直接 Binding
- <android.support.v4.widget.DrawerLayout
- android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”
- app:scrimColor=“@{@color/scrim}”
- app:drawerListener=“@{fragment.drawerListener}”/>
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
重命名 Setters(Renamed Setters)
- @BindingMethods({
- @BindingMethod(type = “android.widget.ImageView”,
- attribute = ”android:tint”,
- method = ”setImageTintList”),
- })
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
上面的例子,开发者不太可能重命名编译程序,Android 框架属性已经实现了
- @BindingAdapter(“android:paddingLeft”)
- public static void setPaddingLeft(View view, int padding) {
- view.setPadding(padding,
- view.getPaddingTop(),
- view.getPaddingRight(),
- view.getPaddingBottom());
- }
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding 适配对其他定制类型非常有用,例如,自定义 loader 可以用异步载入图像
你也可以创建可以接收多个参数的适配器:
- @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);
- }
@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}”
- app:error=“@{@drawable/venueError}”/>
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
如果对于一个 ImageView imageUrl 和 error 都被使用,并且 imageUrl 是一个 String 类型以及 error 是一个 drawable 时,该适配器被调用
- 匹配的过程中自定义 name spaces 将被忽略
- 你也可以为 Android name spaces 写适配器
- @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());
- }
- }
@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());
}
}
事件处理程序只能用一个抽象方法与接口或抽象类一起使用,例如:
- @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);
- }
- }
- }
@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);
- }
@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() {
- @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);
- }
- }
- }
@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 使用添加和删除的监听者而不是为 View.OnAttachStateChangeListener 设置方法,android.databinding.adapters.listenerutil 类有助于保持跟踪,他们可能会在绑定适配器删除以前的 listener
- <TextView
- android:text=‘@{userMap[“lastName”]}’
- android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”/>
<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”/>
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,背景需要 Drawable 对象,但颜色是一个整数,不管何时有 Drawable 并且返回值是一个整数,那么整数类型会被转换为ColorDrawable 看,这个转换是通过使用带带有 BindingConversion 注解的静态方法完成的:
- @BindingConversion
- public static ColorDrawable convertColorToDrawable(int color) {
- return new ColorDrawable(color);
- }
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
注意,转换只发生在 setter 级别,所以它不允许混合以下类型:
- <View
- android:background=“@{isError ? @drawable/error : @color/white}”
- android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”/>
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- 语法高亮
- 表达式语言语法错误的标记
- XML 代码完成
- 引用和快速文档
- <TextView android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”
- android:text=“@{user.firstName, default=PLACEHOLDER}”/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
如果你需要你的项目的设计阶段中显示一个默认值,你也可以使用工具的属性而不是默认的表达式的值,在设计 layout 描述属性