[译]Data Binding

本文为官网文章翻译 原文地址
这篇文档将展示如何使用 Data Binding 库声明布局以及如何最低化应用逻辑和布局之间的耦合。
Data Binding Library 非常灵活并且具有很高的兼容性---对于这个兼容库,你可以使用Android2.1以上的所有版本的Android平台。
为了使用 data binding,Gradle的 android插件需要是1.5.0-alpha1或者更高。

构建环境

为了使用 Data Binding,通过sdk manager下载 support repository 中的依赖库。
为了配置app应用 data binding,在 app module 的 build.gradle 文件中,添加 dataBinding 元素。
使用下面的代码片段进行data binding 的配资:
android{
....
dataBinding{
enabled=true
}
}
如果你的app module依赖于一个使用了 data binding 的库,你的 app module 也必须在build.gradle 文件中配置 data binding。
同样,你需要确认你的android studio 应该不低于1.3

Data Binding 布局文件

第一个data binding 表达式。

data binding布局文件和普通布局文件稍微有些不同。它以layout为根标签(root tag),随后紧跟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属性设置为设置用户名。

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

Data 对象

假设有一个User普通类

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

这个类型中的数据不能够改变。在一个应用中,数据一旦被读取就不再改变是很常见的场景。当然,也可以使用下面的JavaBeans

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}被用来设置TextView的text属性。对于前面的类来说,通过直接访问 firstName 属性的方式;对于后面的类来说,通过getFirstName()的方法。当然,也可以通过firstName()来解决这个问题,如果这个方法存在的话。

绑定数据

默认的,绑定类将被创建,它的名字以布局文件名为基础,遵循Pascal原则,以"Binding"为后缀。比如,上面的布局文件名为 main_activity.xml,因此将产生的类就是MainActivityBinding。这个类持有所有从特征(比如user变量)到布局view的绑定,并且知道怎样为绑定表达式赋值。创建绑定的最简单的方式,就是在它被inflating的时候。

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

完成。另外,可以通过下面的方式来得到view:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
View view = binding.getRoot();

如果你在一个ListView或者RecyclerView的adapter中使用数据绑定,推荐使用:

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

事件处理

Data Binding允许写表达式来处理view事件(比如onClick)。事件属性名和监听器(listener)方法一致(也有一些例外)。举个栗子,View.OnLongClickListener 有 onLongClick()方法,所以,该事件的属性名为 android:onLongClick。属性名所对应的,就是该事件的处理方法。下面介绍两种方式来处理事件:

  • 方法引用(Method References):在表达式中,可以引用方法,该方法应该遵守监听器方法签名的特征(译者:比如参数个数、参数类型、返回值类型、访问限制等)。当一个表达式被认为是对一个方法的引用,Data Binding 将把这个引用方法和所有者对象包裹进一个监听器中,并将该监听器设置到目标view上。
  • 监听绑定(Listener Bindings):当事件发生时,兰姆达表达式将被执行。Data Binding会在view上创建一个监听器,当事件被分发下来,监听器将计算兰姆达表达式。

方法引用

事件可以直接和事件处理器关联绑定,就是android:onClick被指派到Activity中的onClick方法一样。和View#onClick属性相比,方法引用的优势在于,表达式将在编译的时候进行处理,因此,如果方法不存在或者签名不正确,将报编译期错误。
方法引用和监听器绑定主要的不同在于,方法引用中,真正的监听的实现是在数据被绑定的时候,而不是在事件被触发的时候。如果你更喜欢在事件发生的时候计算表达式,应该是用监听绑定(listener binding)
为了给事件指定处理器handler,应该使用一般的绑定的表达式,它的值就是处理事件的方法名。举个栗子

public class MyHandlers {
    public void onClickFriend(View 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.Handlers"/>
       <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以上。
在方法引用中,方法的参数必须匹配事件监听器方法。在监听器绑定中,仅仅只需要返回值和监听器方法的返回值匹配即可。举个栗子

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>

监听器被表现为兰姆达表达式,兰姆达表达式必须作为整个表达式的根元素。当在表达式中使用回调函数,Data Binding将自动创建必要的监听并注册在事件上。当view上的事件发生,Data Binding将计算给定的表达式。
注意,在上面的栗子中,我们并没有定义要传入到onClick(View view)中的view参数。监听器绑定对于监听器参数提供了两种选择:如果不需要监听器(各种listener)参数,你可以选择忽略方法中的所有参数不写,如果需要使用时,应该写出使用所有监听器参数。举个栗子:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"
android:onClick="@{() -> presenter.onSaveClick(task)}"

第一种写了监听器参数,第二种没有写,但是它们是等价的,因为在事实上,处理方法onSavaClick中并没有需要view。
如果你想在表达式中使用监听器参数,可以这样写:

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

你可以在兰姆达表达式中使用多个参数:

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,你的表达式要注意返回值的一致性.例如,如果你想监听一个long click事件,你的表达式应该返回一个布尔值:

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

如果表达式因为null对象而不能进行计算,Data Binding 会返回该类型下默认的Java值。对于引用类型是null,对于int就是0,对于布尔值就是false。

Data Objects

任何普通对象(POJO)都可以用来进行数据绑定。但是修改一个POJO对象并不能触发UI的更新。数据绑定真正的力量在于当数据更改时UI层能得到更新。这里,将提供三种不同的数据更改通知机制:Observable objects, observable fields 和 ovservable collections.
当这些之中的一个任意一个observable 数据对象被绑定到 UI,当数据对象发生变化时,UI将得到自动更新。

Observable Objects

当一个类实现了Observable接口,这将
Observable接口有一种机制增添和删除listener,但是,nofitying取决于开发者。为了简化开发工作,一个基类,BaseObservable被创建用来实现listener的注册机制。而数据类只需要负责当属性发生的变化的时候进行通知。这个做法,通过对getter方法进行Bindable注解,在setter中进行通知来完成。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

Bindable注解会在编辑期间产生进入BR类文件的入口。BR类文件被创建在module package中。

ObservableFields

创建一个Observable对象需要做一些工作,因此,如果开发者想要节省时间或者需要更改的属性很少,就可以使用ObservableField。相似的,还有ObserableBoolean,ObserableByte,ObservableChar,ObservableShort,ObservableInt,ObserableLong,ObserableFloat,ObservableDouble,ObservableParcelable等。ObservableFields 是一种自包含的被观察者对象,拥有一个field。

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

为了访问值,使用set和个体方法:

user.firstName.set("Google");
int age = user.age.get();

Observable Collections

有些应用使用更加动态的结构来持有数据。Observable collections允许通过键值对访问这些数据。当键为引用类型时,ObservableArrayMap将是非常有用的。比如,当key为字符串时:

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在布局文件中,可以通过key来访问数据:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

当key为Integer时,ObservableArrayList是非常有用的:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在布局文件中,应该这样通过索引访问:

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

推荐阅读更多精彩内容