<>Android Jetpack架构篇:Data Binding(一)

<>Layouts and binding expressions(布局和绑定表达式)

该表达式语言,允许你通过编写表达式来处理视图的事件分发。数据绑定库(Data Binding
Library)自动生成将布局中的视图与data对象绑定所需的类。

数据绑定布局文件略有不同,必须以 layout 标记开头,后跟 data 元素和 view 根元素。 view
元素必须是一个非绑定的布局文件。下面是布局文件样例:
<?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 元素中的变量user描述了可在此布局中使用的属性。
<variable name="user" type="com.example.User" />
布局中的使用 “@{}” 语法来使用属性。这里,TextView文本设置为user变量的 firstName属性:
<TextView android:layout_width="wrap_content" android:layout_height=
"wrap_content" android:text="@{user.firstName}" />
**注意:**布局表达式应保持短小且简单,因为它们不能进行单元测试并且IDE支持有限。为了简化布局表达式,您可以使用自定义绑定适配器。

<>Data object(数据对象)

假设有一个普通的实体对象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 { 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; } }
从数据绑定的角度来看,这两个类是等价的。@{user.firstName}用于该android:text 属性的表达式访问firstName
前一类中的字段和**getFirstName()**后一类中的 方法。或者,**firstName()**如果存在该方法,也可以解决。

<>Binding data(绑定数据)

为每个布局文件生成绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。上面的布局文件名是
activity_main.xml相应生成的类
MainActivityBinding。此类包含布局属性(例如,user变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。建议绑定的推荐方法是在扩展布局时执行此操作,如图所示在以下示例中:
Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(
savedInstanceState); MainActivityBinding binding = DataBindingUtil.
setContentView(this, R.layout.activity_main); User user = new User("Test",
"User"); binding.setUser(user); }
**注意:**上面MainActivityBinding是官网中写法,正确写法是ActivityMainBinding请读者自行替换,下同。

在运行时,应用程序在UI中显示Test用户。或者,您可以使用a获取视图LayoutInflater,如以下示例所示:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果您在 Fragment, ListView或RecyclerView适配器中使用数据绑定,则可能更适合使用 inflate() 绑定类或
DataBindingUtil类的方法,如以下代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater,
R.layout.list_item, viewGroup, false);
<>表达语言

<>一般特征

表达式语言看起来很像托管代码中的表达式。您可以在表达式语言中使用以下运算符和关键字:

* 基本运算 + - / * %
* 字符串连接 +
* 逻辑表达式 && ||
* 二进制 & | ^
* 一元运算符 + - ! ~
* 移位 >> >>> <<
* 比较运算 == > < >= <=
* instanceof
* 分组 ()
* 字面值 - 字符,字符串,数字, null
* 强转
* 方法调用
* 属性访问
* 数组访问 []
* 三元运算符 ?:
举例:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ?
View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
<>缺少操作

您可以在托管代码中使用的表达式语法中缺少以下操作:

* this
* super
* new
* 显式通用调用
<>空结合运算符

空合并运算符(??)如果不是,则选择左操作数,如果是null 前者,则选择右操作数null。
android:text="@{user.displayName ?? user.lastName}"
这在功能上等价于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
<>属性引用

表达式可以使用以下格式引用类中的属性,这对于字段,getter和ObservableField 对象是相同的 :
android:text="@{user.lastName}"
<>空指针异常避免

生成的数据绑定代码会自动检查null值并避免空指针异常。例如,在表达式中@{user.name <http://user.name>},如果
user为null,user.name则为其分配其默认值null。如果您引用user.ageage,其中age是类型int,则数据绑定使用默认值0。

<>集合

可以使用[]运算符访问普通集合,例如arrays、lists、sparse lists和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]}"
注意:您还可以使用object.key表示法引用map中的值。例如,**@{map[key]}**在上面的示例中可以替换为 @{map.key}。

<>字符串文字

您可以使用单引号括起属性值,这允许在表达式中使用双引号,如以下示例所示:
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)}"
某些资源需要显式类型求值,如下表所示:

类型 正常引用 表达式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList
<>事件处理


数据绑定允许您编写从视图调度的表达式处理事件(例如,onClick()方法)。事件属性名称由监听器方法的名称确定,但有一些例外。例如,View.OnClickListener有一个方法onClick(),所以这个事件的属性是android:onClick。

对于click事件,有一些专门的事件处理程序需要除android:onClick避免冲突之外的属性。您可以使用以下属性来避免这些类型的冲突:

Class Listener setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut
您可以使用以下机制来处理事件:

*
方法引用:在表达式中,您可以引用符合监听器方法签名的方法。当表达式求值为方法引用时,Data绑定将方法引用和所有者对象包装在监听器中,并在目标view上设置该监听器。如果表达式求值为null,则数据绑定不会创建监听器并设置null监听器。
* 监听器绑定:这些是在事件发生时计算的lambda表达式。数据绑定总是创建一个监听器,它在view上设置。调度事件时,监听器会计算lambda表达式。
<>方法引用

事件可以直接绑定到处理程序方法,类似于android:onClick 可以分配给活动中的方法的方式。与View onClick属性相比的一个主要优点
是表达式在编译时处理,因此如果该方法不存在或其签名不正确,则会收到编译时错误。

方法引用和监听器绑定之间的主要区别在于,实际的监听器实现是在绑定数据时创建的,而不是在触发事件时创建的。如果您希望在事件发生时评估表达式,则应使用监听器绑定。

要将事件分配给其处理程序,请使用普通绑定表达式,其值为要调用的方法名称。例如,请考虑以下示例数据对象:
public class MyHandlers { public void onClickFriend(View view) { ... } }
绑定表达式可以将View的单击监听器分配给 onClickFriend()方法,如下所示:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android=
"http://schemas.android.com/apk/res/android"> <data> <variable name="handlers"
type="com.example.MyHandlers"/> <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>
注意:表达式中方法的签名必须与监听器对象中方法的签名完全匹配。

<>监听器绑定

监听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但它们允许您运行任意数据绑定表达式。适用于Gradle版本2.0及更高版本的Android
Gradle插件提供此功能。


在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值匹配(除非它期望void)。例如,请考虑以下具有该onSaveClick()
方法的Presenter 类:
public class Presenter { public void onSaveClick(Task task){} }
然后,您可以将click事件绑定到onSaveClick()方法,如下所示:
<?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>

在表达式中使用回调时,数据绑定会自动创建必要的监听器并注册事件。当View触发事件时,数据绑定会计算给定的表达式。与常规绑定表达式一样,在计算这些监听器表达式时,仍然可以获得数据绑定的null和线程安全性。


在上面的示例中,我们尚未定义view传递给的参数onClick(View)。监听器绑定为监听器参数提供了两种选择:忽略方法的所有参数,也可以命名所有参数。如果为参数命名,则可在表达式中使用它们。例如,上面的表达式可以写成如下:
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)}" />
如果监听事件返回的类型不是void,则你的表达式也必须返回相同类型的值。例如,如果要监听长按事件,则应返回表达式boolean。
public class Presenter { public boolean onLongClick(View view, Task task){} }
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于null对象而无法计算表达式,则数据绑定将返回该类型的默认值。例如:引用类型为null对于参考, int为0,boolean为false等。

如果需要使用带断言的表达式(例如:三元运算),则可以使用void。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
<>避免复杂的监听器


监听器表达式非常强大,可以使您的代码非常容易阅读。另一方面,包含复杂表达式的监听器使您的布局难以阅读和维护。这些表达式应该像将UI中的可用数据传递给回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。

<>Imports、variables和includes

数据绑定库提供导入,变量和包含等功能。导入使布局文件中的类很容易引用。变量允许您描述可用于绑定表达式的属性。包括让您在整个应用中重复使用复杂的布局。

<>Imports

导入允许您轻松引用布局文件中的类,就像在托管代码中一样。import可以在data 元素内使用零个或多个元素。以下代码示例将View类导入布局文件:
<data> <import type="android.view.View"/> </data>
导入的View类,可以在绑定表达式中引用它。以下示例显示如何引用View类的常量VISIBLE和GONE:
<TextView android:text="@{user.lastName}" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:visibility="@{user.isAdult ?
View.VISIBLE : View.GONE}"/>
<>Type aliases(类型别名)

当存在类名冲突时,可以将其中一个类重命名为别名。以下示例将包中的View类 重命名com.example.real.estate为Vista:
<import type="android.view.View"/> <import type="com.example.real.estate.View"
alias="Vista"/>
这样,可以用Vista来引用com.example.real.estate.View,而View引用android.view.View。

<>导入其他类

导入的类型可以用作变量和表达式中的类型引用。以下示例显示User和List用作变量的类型:
<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问题。

您还可以使用导入的类型来进行强转。以下示例将connection属性强制类型转换为User:
<TextView android:text="@{((User)(user.connection)).lastName}" android:
layout_width="wrap_content" android:layout_height="wrap_content"/>
在表达式中引用静态字段和方法时,也可以使用导入的类型。以下代码导入MyStringUtils类并引用其capitalize方法:
<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.lang.*会自动导入。

<>variables(变量)

您可以variable在元素内使用多个元素data。每个
variable元素都描述了一个属性,该属性可以在布局中设置,以便在布局文件中的绑定表达式中使用。下面的示例声明的user,image和note变量:
<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 collection,则应该在类型中反射。如果变量的基类或接口未实现
Observable接口,则不会观察变量。

当存在用于各种配置的不同布局文件(例如,横向或纵向)时,组合变量。这些布局文件之间不得存在冲突的变量定义。

生成的绑定类对于每个描述的变量都有一个setter和getter。调用setter之前,变量采用托管代码默认值 –
引用类型为null,int为0,boolean为false等。


特殊变量context,可由绑定表达式生成。context的值是由根View的getContext()方法获取的Context对象。context变量由具有该名称的显式变量声明覆盖。

<>includes(包含)

通过使用app命名空间和属性中的变量名,变量可以从包含的布局传递到包含的布局绑定中。以下示例显示user了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> <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>
数据绑定不支持include作为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><!-- Doesn't work --> <include layout=
"@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user
="@{user}"/> </merge> </layout>
参考
[1] https://developer.android.com/topic/libraries/data-binding/
<https://developer.android.com/topic/libraries/data-binding/>
[2] https://developer.android.com/topic/libraries/data-binding/expressions
<https://developer.android.com/topic/libraries/data-binding/expressions>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信