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

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

数据绑定布局文件略有不同,它们以layout的根标记开始,后跟一个data元素和一个view根元素。 此视图元素是您的根将位于非绑定布局文件中的元素。 以下代码显示了一个示例布局文件:



   
       
   
   
       
       
   

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


使用@ {}语法将布局内的表达式写入属性属性中。 在这里,TextView文本设置为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。


    
    
    
    
    
    
    
    

…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

注意:为了使XML在语法上正确,必须对<字符进行转义。 例如:您必须编写List而不是List

您还可以使用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()方法,如下所示:



   
       
       
   
   
       
   

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

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

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

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

然后,可以将click事件绑定到onSaveClick()方法,如下所示:



    
        
        
    
    
        

在表达式中使用回调时,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){}
}

如果您正在监听的事件返回的值的类型不为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传递到回调方法一样简单。 您应该在从侦听器表达式调用的回调方法内实现任何业务逻辑。

你可能感兴趣的:(【Android Architecture】Data Binding向导之布局和绑定表达式)