布局和绑定表达式

通过表达式语言,您可以编写用于处理分派的事件的表达式 观看次数。数据绑定库会自动生成所需的类 将布局中的视图与您的数据对象绑定。

数据绑定布局文件略有不同,以 layout,后跟 data 元素和 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}" />

数据对象

假设您有一个用于描述 User 实体的普通对象:

Kotlin

data class User(val firstName: String, val lastName: String)

Java


public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

此类型的对象拥有永不改变的数据。在应用中 读取一次,之后再也不会更改的数据。您还可以使用 一个遵循一组约定的对象,比如在 Google Analytics 中使用访问器方法, Java 编程语言,如以下示例所示:

Kotlin

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

Java

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()(如果存在该方法)。

绑定数据

系统会为每个布局文件生成一个绑定类。默认情况下, 类基于布局文件的名称,转换为 Pascal 大小写形式,其中 Binding 后缀。例如,前面的布局文件名为 activity_main.xml,因此生成的相应绑定类为 ActivityMainBinding

此类包含布局属性中的所有绑定,例如, user 变量 - 添加到布局的视图,并知道如何赋值 为绑定表达式指定的值。我们建议在进行膨胀时创建绑定 布局,如以下示例所示:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

在运行时,应用会在界面中显示 Test 用户。或者,您也可以 使用 LayoutInflater,如 示例:

Kotlin

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

如果您在 FragmentListViewRecyclerView 则建议您使用 inflate() 绑定类或 DataBindingUtil 类,如 如以下代码示例所示:

Kotlin

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

Java

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

表达式语言

常见功能

表达式语言与托管代码中的表达式非常相似。您 可以在表达式语言中使用以下运算符和关键字:

  • 数学符号:+ - / * %
  • 字符串串联:+
  • 逻辑:&& ||
  • 二进制文件:& | ^
  • 一元:+ - ! ~
  • Shift 键:>> >>> <<
  • 比较运算符:== > < >= <=< 需要转义为 &lt;
  • instanceof
  • 分类:()
  • 字面量,如字符、字符串、数字、null
  • Cast
  • 方法调用
  • 字段访问
  • 数组访问:[]
  • 三元运算符:?:

以下是一些示例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

缺少的运算

您可以使用的表达式语法中缺少以下运算 使用以下代码:

  • this
  • super
  • new
  • 显式泛型调用

Null 合并运算符

如果左操作数不是 null,则 null 合并运算符 (??) 会选择左操作数 或右边的 ID(如果前者为 null):

android:text="@{user.displayName ?? user.lastName}"

这在功能上等同于以下代码:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

属性引用

表达式可以使用以下格式引用类中的属性, 该字段对于字段、getter 和 ObservableField 对象:

android:text="@{user.lastName}"

避免 null 指针异常

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

视图引用

表达式可以使用以下代码,按 ID 引用布局中的其他视图。 语法:

android:text="@{exampleText.text}"

在以下示例中,TextView 视图引用了 EditText 中的 相同布局:

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>

合集

您可以访问常见集合,例如数组、列表、稀疏列表和 映射,为方便起见使用 [] 运算符。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;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[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)}"

您可以通过传递属性引用视图 引用作为资源参数:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

当一个复数带有多个形参时,请传递所有形参:


  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

有一些针对点击事件的专用事件处理脚本,需要 属性(除 android:onClick 以外),以免发生冲突。您可以使用 以下属性以避免此类冲突:

监听器 setter 属性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

您可以使用这两种机制,具体请参阅 用于处理事件:

  • 方法引用:在表达式中,您可以 引用方法(符合监听器方法签名)。时间 表达式的计算结果为方法引用时,数据绑定会封装方法 在监听器中引用对象和所有者对象,并在 目标视图。如果表达式的计算结果为 null,则数据绑定不会 而是创建监听器并设置 null 监听器。
  • 监听器绑定:这些是 lambda 表达式, 在事件发生时进行评估。数据绑定始终会创建一个 监听器,并在视图上设置该监听器。分派事件后, 监听器对 lambda 表达式求值。

方法引用

您可以将事件直接绑定到处理程序方法,这与 分配 android:onClick更改为 方法。与 View onClick 属性是指 处理表达式。因此,如果该方法不存在或 签名不正确,您会收到编译时错误。

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

要将事件分配给其处理脚本,请使用普通绑定表达式,将 value 为要调用的方法名称。例如,请参考以下示例 布局数据对象:

Kotlin

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

Java

public class MyHandlers {
    public void onClickFriend(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>

监听器绑定

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

在方法引用中,方法的参数必须与 事件监听器。在监听器绑定中,只有返回值必须与 监听器的预期返回值,除非预期值为 void。对于 以下面的 Presenter 类为例,该类具有 onSaveClick() 方法:

Kotlin

class Presenter {
    fun onSaveClick(task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(Task task){}
}

您可以将点击事件绑定到 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>

在表达式中使用回调时,数据绑定会自动创建 必要监听器,并为该事件注册监听器。当视图触发 事件,数据绑定会对给定的表达式求值。与常规绑定一样 表达式中,您可以获得数据绑定的 null 和线程安全性, 正在计算监听器表达式。

在前面的示例中,传递到 onClick(View)view 形参 没有定义。监听器绑定提供两种监听器参数选项: 您可以忽略方法的所有参数,也可以命名所有参数。如果您愿意 您可以在表达式中使用它们例如,您 可将上述表达式编写为:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

如果要在表达式中使用参数,可以按如下方式操作:

Kotlin

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

您可以使用包含多个形参的 lambda 表达式:

Kotlin

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

Java

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 的值,则您的 表达式还必须返回相同类型的值。例如,如果您希望 倾听触摸和按住(长按)事件,表达式必须返回 布尔值。

Kotlin

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Java

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果由于 null 对象而无法对表达式求值,则数据绑定会返回 该类型的默认值,如 null 表示引用类型,0 表示引用类型 int,或 falseboolean)。

如果您需要将表达式与谓词(例如, 三元类型,您可以使用 void 作为符号:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

避免使用复杂的监听器

监听器表达式功能强大,可让您的代码更易于阅读。在 另一方面,包含复杂表达式的监听器会使布局更难 来读取和维护让表达式像传递可用数据一样简单 从界面传递到回调方法在 从监听器表达式调用的回调方法。

导入、变量和包含

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

导入

通过导入,您可以在布局文件内(例如在托管代码中)引用类。 您可以在 data 元素中使用零个或多个 import 元素。通过 以下代码示例将 View 类导入布局文件:

<data>
    <import type="android.view.View"/>
</data>

导入 View 类可让您从绑定表达式中引用该类。 以下示例展示了如何引用 VISIBLEView 类的 GONE 常量:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

类型别名

当类名称发生冲突时,您可以将其中一个类重命名为 别名。以下示例将 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.ViewView 在布局文件中引用 android.view.View

导入其他类

您可以将导入的类型用作变量和表达式中的类型引用。通过 以下示例展示了用作变量类型的 UserList

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User>"/>
</data>

您可以使用导入的类型对表达式的一部分进行类型转换。以下 以下示例将 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.*

变量

您可以在 data 元素中使用多个 variable 元素。每个 variable 元素描述可在要使用的布局上设置的属性 在布局文件内的绑定表达式中使用。以下示例声明了 userimagenote 变量:

<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 接口,则变量不会

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

生成的绑定类针对描述的每个 变量。这些变量采用默认的托管代码值,直到 setter 方法 调用 - null 表示引用类型,0 表示 intfalse 表示引用 boolean

生成了一个名为 context 的特殊变量,用于绑定表达式 。context 的值为 Context 对象 getContext() 方法。通过 context 变量会被显式变量声明替换, 名称。

包含

您可以将变量从包含元素传递到包含布局的绑定中, 通过在属性中使用应用命名空间和变量名称来调整布局。通过 以下示例显示了来自 name.xmluser 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>

其他资源

如需详细了解数据绑定,请参阅下面列出的其他资源。

示例

Codelab

博文