如何使用Data Binding Library(一)

1.前言


不知道是否还记得前段时间讲得Android三种主流开发框架,要是忘了,可以回顾一下。当时提到了MVVM框架较MVP的优势在于将Model和View实现了双向绑定,使得两者之间可以互相传递变化,减轻了Presenter的压力。谷歌提供了Data Binding库来实现绑定操作,降低了开发的难度,详细信息可以看官方开发库。若是觉得英文不方便,有牛人已经翻译了文档
  但是由于内容较多,初次接触时不容易抓住重点。我结合自己的理解,大概地说一下思路,希望能帮助到大家。

2.核心思想


既然要实现双向绑定,数据的传递必不可少。View层主要是布局的同时可以做简单的操作,而不仅仅是配置界面;Model层则是加入观察机制,当数据变化时通知界面刷新。
  从编码层次来说,一方面向XML中引入了类、变量、表达式和事件处理。其中,变量可以承载数据,类(常量及静态方法)配合表达式控制展示,事件处理用于响应交互。另一方面实体类或其成员变量使用Observable接口,使setters中封装了通知方法,可以与界面交互。最终在Activity、Fragment或Adapter等需要界面和数据的地方,为两者建立联系。

3.类和变量


想在XML中写代码,首先得解决数据来源问题,所以引入<layout><data><variable><import>这几个标签。根据以下代码来讲解一下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <import type="android.view.View"/>
       <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}"
           android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
   </LinearLayout>
</layout>
3.1.layout和data标签

在Data Binding中,使用<layout>作为根标签包裹两个部分,其一就是<data>包裹的数据区,其二自然是以前的界面布局。由此可推断,外界将数据传入数据区声明的变量中,再由界面布局的表达式调用,最后赋值给控件属性
  数据是在Java代码中以对象的形式传入,所以在代码中得有类来表示XML。不用操心,它由Data Binding自动生成,根据布局文件的名称,转化为驼峰命名方式,并添加“Binding”为后缀。默认放在模块包的databinding目录下,可通过在XML中为<data>添加class属性来修改。至于使用,后面在Binding对象中将详细说明。

// 只是修改生成类的名称
<data class="ContactItem">
    ...
</data>
// 放置在相对模块包的路径下,并重命名
<data class=".ContactItem">
    ...
</data>
// 使用绝对路径,与完整类名一致
<data class="com.example.ContactItem">
    ...
</data>
3.2.variable标签

用来声明XML中使用的变量,由两个属性组成,name描述在表达式中引用的名称;type则是引用的类型。注意的是,type的值除了java.lang.*包下的类型不需要全路径,其它都得写完整。为了避免空指针,Data Binding提供了缺省值,引用类型为null,int类型为0,boolean类型为false等。同时自动根据根视图提供context对象,当同名时将被覆盖。

当有多种不同配置的layout文件时(如,横向或纵向),Variables会被合并,所以这些layout文件之间必须不能有冲突的Variable定义。

3.3.import标签

用来声明XML中使用的类,也有两个属性,type与<variable>的type作用一致,当<import>中声明后,variable中只要使用类名即可;alias是别名,当有多个类名一样时,可以通过它设置别的名字区分,与<variable>的name有些像。

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>

    <import type="android.view.View"/>
    <import type="com.example.real.estate.View" alias="Vista"/>
 </data>

<import>的作用和Java代码中的一样,所以在表达式中可以用于强制类型转换,调用类的静态变量和静态方法。

4.表达式


这是代码的关键部分。为了在XML中写界面相关的逻辑,大部分与Java表达式保持一致,只有以下几点需要注意:

  • @{...}包裹表达式,方便与XML进行区分。
  • 缺少this、super、new和显式泛型调用。
  • 增加Null合并操作android:text="@{user.displayName ?? user.lastName}",相当于android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • 获取对象的属性值,只需要.就够了。虽然Data Binding支持数据模型以公开的成员变量、getters和ObservableFields三种方法对外提供数据(后面数据对象中会详细讲),但框架能自动判断并识别。
  • 非空处理与<variable>的方法保持一样。
  • 常用的集合:arrays、lists、sparse lists和maps,都可以使用[...]来访问,只不过前三个是下标,最后一个是key。
  • 由于属性值通常用双引号包裹,所以字符串可以使用单引号,或者属性值用单引号包裹,字符串用双引号。
  • 按照表达式的规则使用Android资源。对于strings和plurals两种类型,格式化也是可以正常使用的。不熟悉plurals可以看看这篇文章
// 加减乘除等也是可以的
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
// 对字符串进行格式化
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

5.事件处理


事件处理都是基于回调函数的,Data Binding内置线程安全机制。由于是在XML中设置回调函数的逻辑表达式,所以属性名为对应事件监听器的方法名。

这里需要注意区分监听器方法和逻辑表达式,前者才是直接被调用的,后者是内部的实现。

根据监听器创建的时间,将设置方式分为两种:

  • 编译时创建,称为方法引用。从写法上可以理解为用自己的方法替换事件监听器的方法,所以自己方法的参数与事件监听器方法的参数一致。
public class MyHandlers {
    // 与OnClickListener的onClick(View)方法一致
    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.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>
  • 触发时创建,称为监听器绑定。写法上来说,先用lambda表达式构造能替换事件监听器方法的结构(要么忽略所有的参数,要么保留所有的参数),再用正常的表达式调用自己写好的方法(只要求返回值与监听器方法的一致,当监听器方法返回void时,自己方法可以随意)
public class MyHandlers {
    public void onClickFriend(Task task){}
}
// 可以用这个替换
android:onClick="@{() -> handlers.onClickFriend(task)}"

6.数据对象


XML中使用的数据对象是由Java类声明模型,实例化创建数据得出的。根据对外公开数据的方式分为三种类型,前两种是基础但不支持数据绑定,最后这种类型才是Data Binding推荐使用的。

6.1.POJO

使用公开的成员变量来给外界提供访问。

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

贯彻面向对象的原则,私有化成员变量,并提供公开的getters和setters方法来让外界访问。

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;
   }
}
6.3.Observable

Data Binding的要求是当数据变化时,可以通知外界,数据已经变化了。为此,提供了Observable接口,实现的类可以为对象添加一个监听器,来监听所有属性的变化。但是通知需开发人员实现,这是一个通用的逻辑,所以又提供了封装Observable接口逻辑的类和变量。

  • BaseObservable
    将数据模型继承BaseObservable类,为每个getter方法添加@Bindable注解。这样编译时,会在模块包目录下自动生成BR类文件,里面含有被注解的实体。由于基类是不具备通知能力的,所以需在setter方法中调用notifyPropertyChanged()方法来通知监听器操作。
private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getFirstName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyChange(); // 通知改变了所有属性
   }
}
  • ObservableFields
    当不需要对一个类进行监听时,可以使用ObservableField<T>作为属性,对添加的类型进行监听。若添加的是基本类型,直接使用已经封装好的,例如ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble和ObservableParcelable。同理,集合也有专门的封装类ObservableArrayMap和ObservableArrayList。
private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}
// 给对象属性赋值和取值时
user.firstName.set("Google");
int age = user.age.get();

7.Binding对象


Binding类将根据layout文件自动生成,但是使用时需要其对象,而传统开发中,把layout文件生成View或其子对象来使用。由此可以推测,Binding对象是对View的封装,来代替View对象。传统开发中Activity的setContentView()方法,LayoutInflater的inflate()方法以及View的findViewById()方法等在Binding中都有相应的方法。

@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);
}
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
// 通常用于ListView或RecyclerVIew
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
// 不知道Binding类名时
MyLayoutBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.my_layout, viewGroup, false);
// 存在根视图对象时
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
// 不知道Binding类名且存在根视图对象时
MyLayoutBinding binding = DataBindingUtil.bindTo(viewRoot, R.layout.my_layout);
<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}"
           android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:id="@+id/lastName"/>
   </LinearLayout>
</layout>
// layout文件中的带id的视图,将会在Binding对象中生成public final属性
public final TextView firstName;
public final TextView lastName;
// 通过 . 的方式调用
binding.firstName.setText();
binding.lastName.setText();

Binding类为XML中所有<variable>生成setters和getters,再通过其实例调用,就可以实现与布局传输数据。

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

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

8.总结


掌握以上知识点,对于固定界面和基本控件的使用就没有问题了,但Data Binding的作用不止这些。举几个例子大家思考一下,想复用某个界面,那如何给这个公共界面传值;对于每个子项layout都不一样的列表控件,该如何赋值显示等,这些高级的用法会在下一讲中说明。

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

推荐阅读更多精彩内容