【Android Architecture】Data Binding向导之布局和绑定表达式

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

数据绑定布局文件略有不同,它们以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>

数据中的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的支持有限。 为了简化布局表达,您可以使用自定义binding adatpers。

数据对象


现在让我们假设您有一个描述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;
  }
}

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

绑定数据


为每个布局文件生成一个绑定类。 默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。 上面的布局文件名是activity_main.xml,因此相应的生成类是ActivityMainBinding。 此类包含从布局属性(例如user变量)到布局视图的所有绑定,并且知道如何为绑定表达式分配值。创建绑定的推荐方法是在使布局inflate的同时进行绑定,例如 如以下示例所示:

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

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

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

表达式语言


通用特性

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

  • 数学运算符 + - / * %
  • 字符串连接符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <= (Note that < needs to be escaped as <)
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • 三元运算符 ?:
    例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
Missing operations

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

  • this
  • supper
  • new
  • 显式泛型调用
Null合并操作符

null合并操作符(??)如果不为null,则选择左操作数;如果前一个为null,则选择右操作数。

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

功能上等同于

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

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

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

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

集合

为了方便起见,可以使用[]运算符访问常见的集合,例如array,lists,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>"/>
    <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 <String>。

您还可以使用object.key表示法引用映射中的值。 例如,以上示例中的@ {map [key]}可以替换为@ {map.key}

String literals

您可以使用单引号将属性值引起来,这允许您在表达式中使用双引号,如以下示例所示:

android:text='@{map["firstName"]}'

也可以使用双引号将属性值引起来。 这样做时,字符串文字应该用反引号`引起来:

android:text="@{map[`firstName`]}"
Resource

您可以使用以下语法访问表达式中的资源:

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

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

Type Normal reference Expression reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList
Event handling

Data Binding使您可以编写从视图分派的表达式处理事件(例如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

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

  • Method references: 在表达式中,可以引用符合侦听器方法签名的方法。 当表达式对方法引用求值时,数据绑定将方法引用和所有者对象包装在侦听器中,然后在目标视图上设置该侦听器。 如果表达式的计算结果为null,则数据绑定不会创建侦听器,而是设置一个null侦听器。

  • Listener bindings: 这些是事件发生时评估的lambda表达式。 数据绑定始终创建一个侦听器,并在视图上进行设置。 调度事件后,侦听器将评估lambda表达式。

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

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

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

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>

注意:表达式中方法的签名必须与侦听器对象中方法的签名完全匹配。

Listener bindings
Listener bindings是事件发生时运行的绑定表达式。 它们类似于方法引用,但是它们使您可以运行任意数据绑定表达式。 适用于Gradle 2.0版及更高版本的Android Gradle插件提供了此功能。

在方法引用中,方法的参数必须与事件侦听器的参数匹配。 在侦听器绑定中,只有您的返回值必须与侦听器的期望返回值匹配(除非期望返回void)。 例如,考虑以下具有onSaveClick()方法的演示者类:

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>

在表达式中使用回调时,data binding将自动创建必要的侦听器并将其注册为事件。 当视图触发事件时,数据绑定将评估给定的表达式。 像在常规绑定表达式中一样,在评估这些侦听器表达式时,您仍然可以获得数据绑定的null和线程安全性。

在上面的示例中,我们尚未定义传递给onClick(View)的view参数。 Listener binding为侦听器参数提供了两种选择:您可以忽略该方法的所有参数,也可以全部命名。 如果您想命名参数,则可以在表达式中使用它们。 例如,上面的表达式可以编写如下:

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容