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}
}
二、数据绑定布局文件(Data Binding Layout Files)
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><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 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><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="@{handlers::onClickFriend}"/></LinearLayout>
</layout>
监听器绑定:监听器绑定是在事件发生运行时的绑定表达式,它类似于方法引用,但它容许你运行任意的数据绑定表达式,需要注意的是此功能在 Android Gradle2.0 及以上版本可用,在方法引用中,该方法的参数必须与事件监听器的参数相匹配,在监听器绑定中,只有返回值必须与监听器的期望返回值相匹配,示例如下:
- 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}”/>
<TextViewandroid: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”/>
<TextViewandroid: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>
…
<TextViewandroid: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><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 布局文件的用户变量
数据绑定不支持包括合并元素的直接子项。例如,不支持下列布局:
- <?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]}"
字符串常量(String Literals):
- 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 orangeHave %d orangesandroid: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;@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 类文件会在模块包内生成,如果 Data 类的基类不能改变,Observable 接口通过 PropertyChangeRegistry 可以方便的来实现用于存储和有效的通知监听器
- 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();
Observable 集合(Observable Clollections)
- 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>
…
<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 用于键是整数:
- 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>
…
<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 生成(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);
ID 绑定(Views With IDs)
- <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><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: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.DrawerLayoutandroid: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() {@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 使用添加和删除的监听者而不是为 View.OnAttachStateChangeListener 设置方法,android.databinding.adapters.listenerutil 类有助于保持跟踪,他们可能会在绑定适配器删除以前的 listener
- <TextView
- android:text=‘@{userMap[“lastName”]}’
- android:layout_width=“wrap_content”
- android:layout_height=“wrap_content”/>
<TextViewandroid: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”/>
<Viewandroid: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”/>
<Viewandroid:background="@{isError ? @drawable/error : @color/white}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
八、Android Studio 对 DataBinding 的支持(Android Studio Support for Data Binding)
- 语法高亮
- 表达式语言语法错误的标记
- 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 描述属性
Android DataBinding 详解相关推荐
- 【转】Android菜单详解——理解android中的Menu--不错
原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...
- Android菜单详解——理解android中的Menu
前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...
- Android LayoutInflater详解
Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...
- android Fragments详解
android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...
- android WebView详解,常见漏洞详解和安全源码(下)
上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑. 上篇:android WebView详解,常见漏洞详解和安全源码(上) 转载请注明出处:http ...
- android WebView详解,常见漏洞详解和安全源码(上)
这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析. 由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...
- android子视图无菜单,Android 菜单详解
Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...
- Android StateFlow详解
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121913352 本文出自[赵彦军的博客] 文章目录 系列文章 一.冷流还是热流 S ...
- Android SharedFlow详解
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121911675 本文出自[赵彦军的博客] 文章目录 系列文章 什么是SharedF ...
- Android菜单详解(三)——SubMenu和IconMenu
我们在上一篇介绍了如何在Android中创建和响应选项菜单,今天我们将探索子菜单和图标菜单. 子菜单Sub Menu 子菜单提供了一种自然的组织菜单项的方式,它被大量地运用在windows和其他OS的 ...
最新文章
- 细节定成败!汕头网络推广提醒你在做网站内容收录时需注意什么?
- “CObject::operator =”: 无法访问 private 成员(在“CObject”类中声明)
- python图像检测_如何用Python检测图像中的矩形项
- 泛微文档存放在服务器哪个地址,泛微OA根据文档的docid查询文档附件存放的路径...
- 代码实现:给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字。...
- 【Tomcat】安装Tomcat服务器Tomcat的目录结构
- redis客户端使用密码
- Linux低分辨率下时钟中断调用流程
- 中国移动推自有品牌终端利大于弊
- 鸿蒙系统只能从商城安装软件,鸿蒙系统安装不了第三方软件是为什么?鸿蒙系统怎么安装第三方app...
- D3中的each() 以及svg defs元素 clipPath的使用
- 01.初识redis
- 根据特征图画热图_heatmap
- 读 Frank Thilly 之《西方哲学史:修补增订版》
- 小米OJ12月常规赛“数数字”题目详解与SG函数SG定理个人理解
- (2018干货系列八)最新VR学习路线整合
- 国际版阿里云短信对接
- 开源软件项目的定性和定量分析指标 ———— CHAOSS 指标解析
- 基于Lucene垂直搜索引擎的研究与开发实践报告
- 浅谈“头脑风暴法”在软件测试中的运用
热门文章
- NYOJ-366:D的小L
- 使用ffmpeg批量合并flv文件
- 创意小发明:山寨码表.自行车码表的制作 程序原理图,设计图,源代码
- Content type ‘application/json;charset=UTF-8‘ not supp...
- 光盘/硬盘“无法复制:数据错误(循环冗余检查)”的解决方案
- qt quick-QML虚拟软键盘V2版本(手机键盘弹出机制)-支持换肤、动态加载移除语言
- Linux第一个正式版本,你安装的第一个Linux系统是什么版本的,成功没有啊?
- 如何落户北京?19年积分落户规模为6000人!
- 国家区块链漏洞库 《区块链漏洞定级细则》发布
- Kroll任命Richard Davies担任网络风险业务副董事总经理,常驻香港