Android—数据绑定库DataBinding

1、概览

数据绑定库是Android Jetpack的一部分,是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

布局通常是使用调用界面框架方法的代码在 Activity 中定义的。例如,以下代码调用findViewById() 来查找 TextView 控件并将其绑定到 viewModel 变量的 userName 属性:

    TextView textView = findViewById(R.id.sample_text);
    textView.setText(viewModel.getUserName());

以下示例展示了如何在布局文件中使用数据绑定库将文本直接分配到微件。这样就无需调用上述任何 Java 代码。请注意赋值表达式中@{}语法的使用:

<TextView
        android:text="@{viewmodel.userName}" />
    

借助布局文件中的绑定组件,您可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便。还可以提高应用性能,并且有助于防止内存泄漏以及避免发生 Null 指针异常。

注意:在许多情况下,视图绑定可简化实现,提高性能,提供与数据绑定相同的好处。如果您使用数据绑定的主要目的是取代 findViewById() 调用,请考虑改用视图绑定。

最近更新时间当前稳定版下一候选版本Beta 版Alpha 版
2019 年 9 月 5 日3.5.0--3.6.0-alpha10

数据绑定库与 Android Gradle 插件捆绑在一起。您无需声明对此库的依赖项,但必须启用它。

如需启用数据绑定,请在模块的 build.gradle 文件中将 dataBinding 构建选项设置为 true,如下所示:

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

注意:即使模块不直接使用数据绑定,也必须为依赖于使用数据绑定的库的所有模块启用数据绑定。

2、使用入门

数据绑定库不但灵活,而且兼容性广,它是一个支持库,因此您可以在运行 Android 4.0(API 级别 14)或更高级别的设备上使用它。

建议您在项目中使用最新的 Android Plugin for Gradle。不过,1.5.0 版及更高版本支持数据绑定。有关详情,请参阅介绍如何更新 Android Plugin for Gradle的说明。

      编译环境

要开始使用数据绑定,请从 Android SDK 管理器中的支持代码库下载该库。有关详情,请参阅更新 IDE 和 SDK 工具

要将应用配置为使用数据绑定,请在应用模块的 build.gradle 文件中添加 dataBinding 元素,如以下示例所示:

android {
        ...
        dataBinding {
            enabled = true
        }
    }
    

注意:即使应用模块不直接使用数据绑定,也必须为依赖于使用数据绑定的库的应用模块配置数据绑定。

       Android Studio 对数据绑定的支持

Android Studio 支持许多用于修改数据绑定代码的功能。例如,它支持用于数据绑定表达式的以下功能:

  • 语法突出显示
  • 标记表达式语言语法错误
  • XML 代码完成
  • 包括导航(如导航到声明)和快速文档在内的引用

注意:数组和通用类型(如 Observable 类)可能会不正确地显示错误。

Layout Editor 中的 Preview 窗格显示数据绑定表达式的默认值(如果提供)。例如,Preview 窗格会在以下示例声明的 TextView 微件上显示 my_default 值:

<TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.firstName, default=my_default}"/>
    

如果您只需要在项目的设计阶段显示默认值,则可以使用 tools 属性,而不是默认表达式值,详情请参阅工具属性参考中的描述。

3、布局和绑定表达式

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

数据绑定布局文件略有不同,以根标记 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}" />
    

注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器

数据对象

现在我们假设您有一个 plain-old 对象来描述 User 实体:

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

此类型的对象拥有永不改变的数据。应用包含读取一次后不会再发生改变的数据是很常见的。也可以使用遵循一组约定的对象,例如 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;
      }
    }

从数据绑定的角度来看,这两个类是等效的。用于 android:text 特性的表达式 @{user.firstName} 访问前一个类中的 firstName 字段和后一个类中的 getFirstName() 方法。或者,如果该方法存在,也将解析为 firstName()

绑定数据

系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,它会转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding。此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

    @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 获取视图,如以下示例所示:

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

如果您要在 FragmentListView 或 RecyclerView 适配器中使用数据绑定项,您可能更愿意使用绑定类或 DataBindingUtil 类的 inflate() 方法,如以下代码示例所示:

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

表达式语言

常见功能

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

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 &lt;
  • 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,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 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}"

注意:绑定类将 ID 转换为驼峰式大小写。

在以下示例中,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]}"
    

注意:要使 XML 不含语法错误,您必须转义 < 字符。例如:不要写成 List<String> 形式,而是必须写成 List&lt;String>

您还可以使用 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属性
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

您可以使用以下机制处理事件:

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

方法引用

事件可以直接绑定到处理脚本方法,类似于为 Activity 中的方法指定 android:onClick 的方式。与 View onClick 特性相比,一个主要优点是表达式在编译时进行处理,因此,如果该方法不存在或其签名不正确,则会收到编译时错误。

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

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

    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>
    

注意:表达式中的方法签名必须与监听器对象中的方法签名完全一致。

监听器绑定

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

在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值相匹配(预期返回值无效除外)。例如,请参考以下具有 onSaveClick() 方法的 presenter 类:

    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)}"
    

或者,如果您想在表达式中使用参数,则采用如下形式:

    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 的值,则您的表达式也必须返回相同类型的值。例如,如果要监听长按事件,表达式应返回一个布尔值。

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

   

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

如果由于 null 对象而无法对表达式求值,则数据绑定将返回该类型的默认值。例如,引用类型返回 nullint 返回 0boolean 返回 false,等等。

如果您需要将表达式与谓词(例如,三元运算符)结合使用,则可以使用 void 作为符号。

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

避免使用复杂的监听器

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

导入、变量和包含

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

导入

通过导入功能,您可以轻松地在布局文件中引用类,就像在托管代码中一样。您可以在 data 元素使用多个 import 元素,也可以不使用。以下代码示例将 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}"/>
    

类型别名

当类名有冲突时,其中一个类可使用别名重命名。以下示例将 com.example.real.estate 软件包中的 View 类重命名为 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&lt;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.*

变量

您可以在 data 元素中使用多个 variable 元素。每个 variable 元素都描述了一个可以在布局上设置、并将在布局文件中的绑定表达式中使用的属性。以下示例声明了 userimage 和 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 接口的基类或接口,则变量是“不可观察的”。

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

在生成的绑定类中,每个描述的变量都有一个对应的 setter 和 getter。在调用 setter 之前,这些变量一直采用默认的托管代码值,例如引用类型采用 nullint 采用 0boolean 采用 false,等等。

系统会根据需要生成名为 context 的特殊变量,用于绑定表达式。context 的值是根视图的 getContext() 方法中的 Context 对象。context 变量会被具有该名称的显式变量声明替换。

包含

通过使用应用命名空间和特性中的变量名称,变量可以从包含的布局传递到被包含布局的绑定。以下示例展示了来自 name.xml 和 contact.xml 布局文件的被包含 user 变量:

<?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>
    

 

 

相关

databinding 入门 加载本地图片和加载网络图片

android.databinding.tool.util.LoggedErrorException: Found data binding errors.

Android Data Binding实战-高级篇

我在这一节的视频在照着敲了之后,编译时会出现一个错误:

Error:(16, 24) 警告: Application namespace for attribute app:imageUrl will be ignored.
Error:(16, 24) 警告: Application namespace for attribute app:placeholder will be ignored.
  • 1
  • 2

而原因就出在下面代码上

@BindingAdapter({"app:imageUrl","app:placeholder"})
    public static void loadImageForUrl(ImageView view, String url, Drawable drawable) {
        Glide.with(view.getContext()).load(url).placeholder(drawable).into(view);
    }

如果您有其他需要,或者相关内容有什么不完善的地方,请留言给我!!

您也可以加入下方qq群,共同学习进步,感谢参与!!

Android学习交流群:523487222

点击链接加入群【Android学习群】

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值