(三、3)布局和绑定表达式

2018-10-22  本文已影响19人  鹿小纯0831

表达式语言允许您编写处理视图调度的事件的表达式。 数据绑定库自动生成将布局中的视图与数据对象绑定所需的类。

数据绑定布局文件略有不同,以布局的根标签开头,后跟数据元素和视图根元素。 此视图元素是您的根在非绑定布局文件中的位置。 以下代码显示了一个示例布局文件:

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

数据中的user变量描述了可在此布局中使用的属性。

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

布局中的表达式使用“@ {}”语法写入属性属性中。 这里,TextView文本设置为用户变量的firstName属性:

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

注意:布局表达式应保持小而简单,因为它们不能进行单元测试并且IDE支持有限。 为了简化布局表达式,您可以使用自定义绑定适配器。

一、数据对象

我们现在假设您有一个普通的对象来描述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。 此类包含布局属性(例如,用户变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。创建绑定的推荐方法是在扩展布局时执行此操作,如 如下例所示:

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

在运行时,应用程序在UI中显示Test用户。 或者,您可以使用LayoutInflater获取视图,如以下示例所示:

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

如果在Fragment,ListView或RecyclerView适配器中使用数据绑定项,则可能更喜欢使用绑定类或DataBindingUtil类的inflate()方法,如以下代码示例所示:

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

三、表达语言

共同特征

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

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

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

空结合运算符

空合并运算符(??)选择左操作数(如果它不为空)或右(如果前者为空)。

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

这在功能上等同于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性参考

表达式可以使用以下格式引用类中的属性,这对于fields,getters和ObservableField对象是相同的:

android:text="@{user.lastName}"
避免空指针异常

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

集合

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

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

某些资源需要显式类型评估,如下表所示:
略。

四、事件处理

数据绑定允许您编写从视图调度的表达式处理事件(例如,onClick()方法)。 事件属性名称由侦听器方法的名称确定,但有一些例外。 例如,View.OnClickListener有一个onClick()方法,因此该事件的属性是android:onClick。
click事件有一些专门的事件处理程序需要除android:onClick以外的属性以避免冲突。 您可以使用以下属性来避免这些类型的冲突:

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

方法参考

事件可以直接绑定到处理程序方法,类似于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){}
}

然后,您可以将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>

在表达式中使用回调时,数据绑定会自动创建必要的侦听器并为事件注册它。 当视图触发事件时,数据绑定会评估给定的表达式。 与常规绑定表达式一样,在评估这些侦听器表达式时,仍然可以获得数据绑定的null和线程安全性。
上面的示例中,我们尚未定义传递给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的值,则表达式也必须返回相同类型的值。 例如,如果要监听长按事件,则表达式应返回布尔值。

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

如果由于null对象而无法计算表达式,则数据绑定将返回该类型的默认值。 例如,引用类型为null,int为0,布尔值为false等。
如果需要使用带谓词的表达式(例如,三元),则可以使用void作为符号。

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

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

五、引用,变量和包含

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

引用

导入允许您轻松引用布局文件中的类,就像在托管代码中一样。 可以在数据元素内使用零个或多个导入元素。 以下代码示例将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<User>"/>
</data>

警告:Android Studio尚未处理导入,因此导入变量的自动完成功能可能无法在IDE中运行。 您的应用程序仍在编译,您可以通过在变量定义中使用完全限定名称来解决IDE问题。

您还可以使用导入的类型来转换表达式的一部分。 以下示例将连接属性强制转换为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>
    <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-null,0表示int,false表示布尔值等。

根据需要生成名为context的特殊变量以用于绑定表达式。 context的值是来自根View的getContext()方法的Context对象。 上下文变量由具有该名称的显式变量声明覆盖。

包括

通过使用app命名空间和属性中的变量名,变量可以从包含的布局传递到包含的布局绑定中。 以下示例显示了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>
上一篇下一篇

猜你喜欢

热点阅读