Android官方文档之DataBinding库

Posted Jadyli1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android官方文档之DataBinding库相关的知识,希望对你有一定的参考价值。

本文与公众号三七文档库同步。

本文由三七原创翻译,转载前务必联系三七。

英文原文链接:https://developer.android.com/topic/libraries/data-binding/index.html

本文档介绍了如何使用DataBinding库来编写声明式布局,并尽量减少绑定应用程序逻辑和布局所需的中间代码。

DataBinding库提供了灵活性和广泛的兼容性 - 这是一个支持库,所以您可以在Android 2.1(API级别7+)之后的所有android平台上使用它。

要使用数据绑定,Gradle 1.5.0-alpha1或更高版本的Android插件是必需的。请参阅如何为Gradle更新Android插件

构建环境

要开始使用数据绑定,请从Android SDK管理器的支持库中下载DataBinding库。

要配置应用程序以使用数据绑定,请将dataBinding元素添加到应用程序模块(module)的build.gradle文件中。

使用下面的代码片段来配置数据绑定:

android 
    ....
    dataBinding 
        enabled = true
    

如果您的应用程序模块依赖了使用数据绑定的库,则您的应用程序模块也必须在其build.gradle文件中配置数据绑定。

另外,请确保您使用的是Android Studio的兼容版本。 Android Studio 1.3及更高版本支持数据绑定,如Android Studio数据绑定支持中所述。

数据绑定编译器V2

3.1.0 Canary 6版本的Android Gradle插件附带一个可选的新编译器。要开始使用它,请更新您的gradle.properties文件以包含以下行:

  android.databinding.enableV2=true

在编译器v2中:

  • ViewBinding类是在java编译器之前由Android Gradle插件生成的。这可以避免由于不相关的原因使得java编译失败进而导致过多误报的错误。
  • 在V1中,编译应用程序时会重新生成库的绑定类(以共享生成的代码并访问最终的BRR文件)。在V2中,库保持其生成的绑定类以及映射器信息,这显著提高了多模块项目的数据绑定性能。

请注意,这个新的编译器是向后不兼容的,所以用v1编译的库不能被v2使用,反之亦然。

V2还会删除一些很少使用的功能来允许这些更改:

  • 在V1中,一个应用程序能够提供绑定适配器,可以覆盖依赖项中的适配器。在V2中,它只会在您自己的模块/应用程序及其依赖项中生效。
  • 以前,如果一个布局文件在两个或多个不同的资源配置中包含一个View具有相同id但不同类的数据,则数据绑定将查找最常见的父类。在V2中,当配置之间的类型不匹配时,它将始终默认为View
  • 在V2中,不同的模块不能在清单文件中使用相同的包名,因为数据绑定将使用该包名来生成绑定映射类。

数据绑定布局文件

编写您的第一套数据绑定表达式

数据绑定布局文件稍有不同,从布局的根标签开始,后跟数据元素和视图根元素。这个视图根元素跟非绑定式布局文件的根元素一样。示例文件如下所示:

<?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)描述了一个可能在此布局中使用的属性。

<variable name="user" type="com.example.User"/>

布局中的表达式使用@语法写入属性参数中。在这里,TextView的文本被设置为userfirstName属性:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@user.firstName"/>

数据对象

现在让我们假设您有一个面向User的简单的Java对象(PO​​JO):

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;
   

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

绑定数据

默认情况下,将根据布局文件的名称生成一个Binding类,将其转换为Pascal格式并将Binding后缀添加到该文件中。上面的布局文件是main_activity.xml,生成类就是MainActivityBinding。这个类将布局属性(例如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中的测试用户。或者,您可以通过以下方式获取视图:

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

如果您在ListViewRecyclerView适配器内使用数据绑定项目,则可能更愿意使用:

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

错误处理

数据绑定允许您编写表达式来处理从视图中分发的事件(例如onClick)。除少数例外,事件属性名称由监听器方法的名称来管理。例如,View.OnLongClickListeneronLongClick()有一个onLongClick()方法,所以这个事件的属性是android:onLongClick。处理事件有两种方法。

  • 方法引用: 在您的表达式中,您可以引用符合监听器方法签名的方法。当表达式评估为方法引用时,数据绑定将方法引用和所有者对象包装在监听器中,并将该监听器设置在目标视图上。如果表达式得出的值为null,则数据绑定不会创建监听器,而是设置空监听器。
  • 监听器绑定: 这些是在事件发生时被计算的lambda表达式。数据绑定总是创建一个监听器,它在视图上设置。事件分发时,监听器计算lambda表达式。

方法引用

事件可以直接绑定到处理方法,类似于android:onClick可以分配给Activity中的方法。与View#onClick属性相比,一个主要的优点是表达式在编译时被处理,所以如果方法不存在或者它的签名不正确,你会收到一个编译时错误。

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

要将事件分配给其处理程序,请使用常规的绑定表达式,其值是要调用的方法名称。例如,如果您的数据对象有两个方法:

public class MyHandlers 
    public void onClickFriend(View 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.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)。例如,您可以有一个具有以下方法的演示者(presenter)类:

public class Presenter 
    public void onSaveClick(Task task)

然后,您可以将click事件绑定到您的类,如下所示:

  <?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和数据绑定的线程安全性。

请注意,在上面的例子中,我们没有定义传入onClick(android.view.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的值,则您的表达式必须返回相同类型的值。例如,如果要监听长按事件,则表达式应该返回boolean

public class Presenter 
    public boolean onLongClick(View view, Task task)
  android:onLongClick="@(theView) -> presenter.onLongClick(theView, task)"

如果由于null对象导致无法计算表达式,数据绑定将返回该类型的默认Java值。例如,null用于引用类型,0用于int类型, false用于boolean类型等。

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

  android:onClick="@(v) -> v.isVisible() ? doSomething() : void"
避免复杂的监听器

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

存在一些专门的点击事件处理程序,它们需要一个属性, 以避免和android:onClick冲突。已经创建了以下属性以避免这种冲突:

监听器设置属性
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

布局细节

导入

import元素中可以使用零个或多个data元素。这些就像在Java中一样可以轻松地引用布局文件中的类。

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

现在,可以在你的绑定表达式中使用View:

<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"/>

现在,Vista可能被用来引用com.example.real.estate.ViewView可能被用来在布局文件内引用android.view.View。导入的类型可以用作变量和表达式中的类型引用:

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

**注意:**Android Studio尚未处理导入,因此导入变量的自动填充可能无法在您的IDE中工作。您的应用程序仍然可以正常编译,您可以通过在变量定义中使用完全限定的名称来解决IDE问题。

<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"/>

就像在Java中一样,java.lang.*会自动导入。

变量

variable元素内可以使用任意数量的data元素。每个variable元素描述可以在布局上设置的属性,以用于布局文件中的绑定表达式。

<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* 接口的基类或接口,变量将不会被检查!

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

生成的绑定类将为每个描述的变量设置一个settergetter。变量将采用默认的Java值,直到setter被调用 - null用于引用类型,0用于int类型, false用于boolean类型等。

根据需要生成一个名为context的特殊变量用于绑定表达式。context值来自根视图的getContext()得到的Context。该context变量将被具有该名称的显式变量声明覆盖。

自定义绑定类名

默认情况下,根据布局文件的名称生成一个Binding类,以大写字母开头,删除下划线(_)并大写下一个单词的首字母,然后添加后缀“Binding”。这个类将被放置在模块包下的databinding包中。例如,布局文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放置在com.example.my.app.databinding

绑定类可以通过调整data元素的class属性来重命名或放置在不同的包中。例如:

<data class="ContactItem">
    ...
</data>

这将在模块包中的databinding包中生成绑定类ContactItem。如果该类应该在模块包内的其他包中生成,则可以用“.”作为前缀:

<data class=".ContactItem">
    ...
</data>

在这种情况下,ContactItem直接在模块包中生成。如果提供完整的包,则可以使用任意包:

<data class="com.example.ContactItem">
    ...
</data>

包含

通过在属性中使用应用程序命名空间和变量名称,变量可以从包含的布局传递到容器的布局的绑定中:

<?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.xmlcontact.xml两个布局文件中都必须有一个user变量 。

数据绑定不支持include作为合并元素的直接子元素。例如, 不支持以下布局:

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

表达式语言

共同特征

表达式语言看起来很像Java表达式。这些是一样的:

  • 数学的 + - / * %
  • 字符串连接 +
  • 逻辑运算符 && ||
  • 二进制 & | ^
  • 一元运算符 + - ! ~
  • 位运算 >> >>> <<
  • 比较运算符 == > < >= <=
  • instanceof
  • 分组 ()
  • 文字 - 字符, 字符串, 数字, null
  • 强转
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元操作符 ?:

例子:

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

缺少的操作

有些你在Java中能使用的表达式语法在这里会缺少一些操作符。

  • this
  • super
  • new
  • 明确的泛型调用

空合并运算符

null合并运算符(??)选择左边(如果不是null)或右边(如果为空)的操作。

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

这在功能上等同于:

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

属性引用

第一种已经在上面的编写您的第一个数据绑定表达式中讨论过了:简短形式的JavaBean引用。当一个表达式引用一个类的属性时,它对字段,getter方法和Observable字段使用相同的格式。

android:text="@user.lastName"

避免NullPointerException

生成的数据绑定代码自动检查空值并避免空指针异常。例如,在表达式中@user.name,如果usernulluser.name将被赋予其默认值(null)。如果你是引用user.age,年龄是一个int,那么它将默认为0。

集合

常见的集合:数组,列表,稀疏列表(sparse lists),和映射集合(map),为了方便访问可以使用[]操作符。

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

数据对象

任何简单的Java对象(PO​​JO)都可以用于数据绑定,但修改POJO不会导致UI更新。当数据改变的时候,您的数据对象能够发出通知,这才是数据绑定的威力。有三种不同的数据更改通知机制: Observable对象observable字段, 和observable集合.

当这些observable数据对象之一被绑定到UI并且数据对象的属性改变时,UI将被自动更新。

Observable对象

实现Observable接口的类将允许附加单个监听器到绑定对象,以监听该对象上所有属性的更改。

Observable接口具有添加和删除监听器的机制,但通知由开发者决定。为了简化开发,创建了一个基类BaseObservable来实现监听器注册机制。数据类实现者仍然负责通知属性何时更改。这是通过给getter分配一个Bindable注解并通知setter来完成的。

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);
   

在编译期间Bindable注解会在BR类中生成一个条目。BR类文件将在模块包中生成。如果数据类的基类不能改变,那么Observable接口可以使用Observable interface may be implemented using the convenient PropertyChangeRegistry帮助类来是实现,以用于存储和高效地通知监听器。

ObservableField

一个小的工作是参与创建Observable类,所以想要节约时间的开发者可能有少数几个属性会用到ObservableField和它的同胞类ObservableBoolean, ObservableByteObservableChar, ObservableShortObservableInt, ObservableLongObservableFloat, ObservableDouble,和ObservableParcelableObservableFields是具有单个字段的独立observable对象。原始版本在访问操作期间避免装箱和取消装箱。要使用,请在数据类中创建一个公共final字段:

private static class User 
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();

就是这样!要访问该值,请使用set和get访问方法:

user.firstName.set("Google");
int age = user.age.get();

Observable集合

一些应用程序使用更加动态化的结构来保存数据。Observable集合允许对这些数据对象进行键存取。当键是String等引用类型时ObservableArrayMap非常有用。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在布局中,可以通过String键访问map:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</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);

在布局中,列表可以通过索引来访问:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</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"/>

生成的绑定

生成的绑定类将布局变量与布局中的视图链接起来。如前所述,绑定的名称和包可能是自定义的。生成的绑定类全部继承自Android11——DataBinding的使用

Android官方数据绑定框架DataBinding

Android,DataBinding的官方双向绑定

Android基础——框架模式MVVM之DataBinding的实践

Android:DataBinding 和 ViewBinding 的区别

Android JetPack组件之DataBinding的使用详解