Google Data Binding Library 谷歌官方数据绑定库(一)

本文为菜鸟学习笔记兼翻译练习用,翻译可能会不准确,细节请以原文为准,如有不足之处还请见谅,若能斧正,小弟不胜感激。原文地址:Google Data Binding Library

数据绑定库(Data Binding Library)

本文旨在介绍如何通过数据绑定库来写布局文件,并用尽量少的必要的胶水代码来将你的业务逻辑和布局绑定在一起。

数据绑定库(Data Binding Library)是一个兼具灵活性和强大兼容性的支持库,你可以在Android2.1之后(API7 Level7+)的任意版本使用它。

为了使用数据绑定库, 你要保证你的Gradle版本在1.5.0-alpha1版本以上, 升级Gradle版本请参考Google官方Gradle升级指南。

搭建环境

开始使用数据绑定库之前, 你需要通过Android SDK manager下载库支持文件。

为了完成你的app对数据绑定库的支持, 你需要在你的app module中的build.gradle文件中添加databinding 元素,添加到gradle的代码如下:

android {
    ....
    dataBinding {
        enabled = true
    }
}

如果你的app module 依赖了某个使用了数据绑定的库,你的app module也需要在它的 build.gradle文件中添加对数据绑定库的支持。

使用数据绑定的布局文件

你的第一个数据绑定例子

使用数据绑定的布局文件有一些细微的不同:它由一个layout标签开始,随后是一个data元素和view根元素。这个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>

用data声明可能在这个布局文件中用到的user变量

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

使用属性参数的语法通过"@{}"表达式书写。这里TextView的text使用的是user对象的firstName属性:

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

数据对象

首先假设你已经定义了一个属于plain-old Java object(POJO)的User对象:

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

这种类型的对象数据永远不会改变。在应用中只读一次并从不改变的对象很常见。我们也可以使用如下的javaBean对象:

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

对于数据绑定来说,上述两种类是相同的。在TextView的 android:text属性中用到的 @{user.firstName}表达式会在第一个类中去使用firstName属性,在第二个类中会去使用getFirstName()方法。如果没有getFirstName()方法,如果存在名为firstName()的方法,数据绑定会使用这个方法。

绑定数据

默认创建的绑定类名字是布局名+Binding后缀,且类型会被转为Pascal类型。比如上边的布局文件是main_activity.xml,则创建的绑定类名称是MainActivityBinding。这个类持有布局中所有和view绑定的内容(比如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上看到User的测试值。或者,你可以这样得到view:

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

如果你在ListView或RecyclerView的适配器中使用数据绑定, 你可以选择下面两种做法:

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

处理事件

数据绑定允许你通过书写表达式处理view发送的事件(比如onClick)。事件属性名称通过监听器的名称管理(个别除外)。比如,View.OnLongClickListeneronLongClick()方法,这个时间的属性名称就是android:onLongClick。有两种方法可以处理控件的事件:

·方法引用:在你的表达式里,你可以引用和监听器签名一致的方法。当表达式判断一个方法引用时,数据绑定会在监听器中包装方法引用和方法对象,并将该监听器设置到目标控件上。如果表达式值为空,数据绑定不会创建监听器并设置一个空监听器(null)代替。

·监听器绑定:当事件发生时执行的是Lambda表达式。数据绑定始终会创建监听器,设置到控件上。当事件传递过来时,监听器执行的是Lambda表达式的内容。

方法引用

事件可以直接绑定事件处理者handler的方法。类似于android:onClick可以委派Activity中的方法一样。与View#onClick属性相比方法引用有一个最大的的店就是表达式是在编译时处理加工的,所以当方法不存在或签名不正确,你会收到一条编译时错误。

方法引用和监听器绑定最大的不同时会有一个真正的监听器在数据绑定时创建,而不是当事件发生时。如果你更愿意在事件发生时执行表达式,你应该选择使用监听器绑定的形式。

为了将事件分配给它的handler,要定义一个标准的表达式,其值作为方法名来调用。例如,如果你的对象有两个方法:

public class MyHandlers {
    public void onClickFriend(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和更高的版本上是支持的。


在使用方法引用时,方法的参数必须和事件的监听器相匹配。在使用监听器绑定方式时,你只需要保证你的返回值和监听器方法的返回值相匹配(除非其返回空)。例如,你可以定义如下一个presenter:

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

这时你可以按下边的方式将点击事件绑定到你的类:

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

由Lamda表达式描述的监听器只允许作为你的表达式的根元素。当表达式用到一个回调时,数据绑定会为事件自动创建一个需要的监听器和寄存器。当控件触发事件, 数据绑定执行给予的表达式。和绑定表达式规则一样,当这些监听器表达式被估值(?)时你仍然可以得到空的线程安全的数据绑定。

注意上边的例子, 我们没有定义传给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, int型返回0,boolean型返回false等等。

如果你需要在一个表达式里使用断言(比如三元表达式),你可以使用void作为一个符号:

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

避免复杂的监听器

监听器功能强大且能使得你的代码更易读。另一方面,包含复合表达式的监听器使得你的布局难以读懂和维护。这些表达式应该尽量简介地从UI获取数据并提供给回调方法。你应当把所有的业务逻辑放在监听器表达式调用的回调方法里。

有些特殊的点击事件需要一个属性名称以避免和android:onClick属性起冲突。下边的属性就是设计用来解决这种冲突的:

ClassListenerSetterAttribute
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut




布局细节

引入(Imports)

在data元素中可能用到0或更多个引入元素。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.View,View可以用来引用 android.view.View。引入的类型可以作为类型引用在变量和表达式中使用。

<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问题。

<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.*是自动引入的。

变量

data元素里可以使用任意数量的变量元素。每个变量元素描述了设置在布局上用于绑定表达式的属性。

<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 collection, 这会反映在类型上。如果变量是一个没有实现Observable接口的基类或接口,这个变量不会被订阅(observed)。

当存在各种不同的对应不同场景的布局文件存在是(比如landscape或portrait),变量会合并。所以它们不能在这些布局文件里存在定义冲突。

创建的绑定类有所有描述过的变量的getter和setter。变量的值是该类型在Java中的默认值(null, 0 , false等),直到其setter被调用。

有一个特殊变量context因为绑定表达式需要而被创建。它的值通过根view的getContext()方法得来。context变量的值会被这个方法得到的显式声明变量覆盖。


OK,这篇先翻译到这里,下一篇从自定义绑定类名(Custom Binding Class Names)开始翻译。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值