Android Jetpack架构篇:Data Binding(一)

Android Jetpack架构篇:Data Binding(一)


Layouts and binding expressions(布局和绑定表达式)

该表达式语言,允许你通过编写表达式来处理视图的事件分发。数据绑定库(Data Binding Library)自动生成将布局中的视图与data对象绑定所需的类。

数据绑定布局文件略有不同,必须以 layout 标记开头,后跟 data 元素和 view 根元素。 view 元素必须是一个非绑定的布局文件。下面是布局文件样例:



   
       
   
   
       
       
   

其中, data 元素中的变量user描述了可在此布局中使用的属性。


布局中的使用 "@{}" 语法来使用属性。这里,TextView文本设置为user变量的 firstName属性:


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


Data object(数据对象)

假设有一个普通的实体对象User:

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

这种类型的对象具有永不改变的数据。在应用程序中,通常会读取一次并且之后不会更改的数据。也可以使用具有约定规则的写法,例如访问器方法的用法,如以下示例所示:

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

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


Binding data(绑定数据)

为每个布局文件生成绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。上面的布局文件名是 activity_main.xml相应生成的类 MainActivityBinding。此类包含布局属性(例如,user变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。建议绑定的推荐方法是在扩展布局时执行此操作,如图所示在以下示例中:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

注意:上面MainActivityBinding是官网中写法,正确写法是ActivityMainBinding请读者自行替换,下同。

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

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

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

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


表达语言

一般特征

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

  • 基本运算 + - / * %
  • 字符串连接 +
  • 逻辑表达式 && ||
  • 二进制 & | ^
  • 一元运算符 + - ! ~
  • 移位 >> >>> <<
  • 比较运算 == > < >= <=
  • 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。

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

这在功能上等价于:

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


属性引用

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

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


空指针异常避免

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


集合

可以使用[]运算符访问普通集合,例如arrays、lists、sparse lists和maps。


    
    
    
    
    
    
    
    

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

注意:您还可以使用object.key表示法引用map中的值。例如,@{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)}"

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

类型 正常引用 表达式引用
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。

对于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

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

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

方法引用

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

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

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

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

绑定表达式可以将View的单击监听器分配给 onClickFriend()方法,如下所示:



   
       
       
   
   
       
   

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

监听器绑定

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

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

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

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



    
        
        
    
    
        

在表达式中使用回调时,数据绑定会自动创建必要的监听器并注册事件。当View触发事件时,数据绑定会计算给定的表达式。与常规绑定表达式一样,在计算这些监听器表达式时,仍然可以获得数据绑定的null和线程安全性。

在上面的示例中,我们尚未定义view传递给的参数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){}
}

如果监听事件返回的类型不是void,则你的表达式也必须返回相同类型的值。例如,如果要监听长按事件,则应返回表达式boolean。

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

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

如果需要使用带断言的表达式(例如:三元运算),则可以使用void。

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


避免复杂的监听器

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


Imports、variables和includes

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


Imports

导入允许您轻松引用布局文件中的类,就像在托管代码中一样。import可以在data 元素内使用零个或多个元素。以下代码示例将View类导入布局文件:


    

导入的View类,可以在绑定表达式中引用它。以下示例显示如何引用View类的常量VISIBLEGONE



Type aliases(类型别名)

当存在类名冲突时,可以将其中一个类重命名为别名。以下示例将包中的View类 重命名com.example.real.estateVista



这样,可以用Vista来引用com.example.real.estate.View,而View引用android.view.View


导入其他类

导入的类型可以用作变量和表达式中的类型引用。以下示例显示UserList用作变量的类型:


    
    
    
    

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

您还可以使用导入的类型来进行强转。以下示例将connection属性强制类型转换为User


在表达式中引用静态字段和方法时,也可以使用导入的类型。以下代码导入MyStringUtils类并引用其capitalize方法:


    
    

就像在托管代码中一样,java.lang.*会自动导入。


variables(变量)

您可以variable在元素内使用多个元素data。每个 variable元素都描述了一个属性,该属性可以在布局中设置,以便在布局文件中的绑定表达式中使用。下面的示例声明的user,image和note变量:


    
    
    
    

在编译时检查变量类型,因此如果变量实现Observableobservable collection,则应该在类型中反射。如果变量的基类或接口未实现Observable接口,则不会观察变量。

当存在用于各种配置的不同布局文件(例如,横向或纵向)时,组合变量。这些布局文件之间不得存在冲突的变量定义。

生成的绑定类对于每个描述的变量都有一个setter和getter。调用setter之前,变量采用托管代码默认值 -- 引用类型为null,int为0,boolean为false等。

特殊变量context,可由绑定表达式生成。context的值是由根View的getContext()方法获取的Context对象。context变量由具有该名称的显式变量声明覆盖。


includes(包含)

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



   
       
   
   
       
       
   

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



   
       
   
   
       
       
   




参考
[1] https://developer.android.com/topic/libraries/data-binding/
[2] https://developer.android.com/topic/libraries/data-binding/expressions

你可能感兴趣的:(Android Jetpack架构篇:Data Binding(一))