DataBinding难点解析之Observable和BindingAdapter

今日科技快讯

近日,有消息称乐视欲裁员10%,实行N+1补偿制度,非上市公司的子板块已经开始着手处理。乐视方面并未予以正面回应,仅转发了乐视董事长贾跃亭11月6日在接受某商业网站专访时的话。贾跃亭当时表态,乐视要全员持股,给员工更高比例的股权。但全员持股背后,是末位淘汰制。“每年末位淘汰定的指标都是8%至10%左右,但其实根本没有做到,希望今年最起码要努力,年底正好进入了全面的价值评估、绩效评估,今年一定要坚决地来做这件事。”

作者简介

本篇是 milter 的第四篇投稿了,之前已经推送过他的基础篇DataBinding实现原理探析(点击可查看),本文着重从两个方面讲解如何使用DataBinding框架,希望对大家有所帮助。

milter 的博客地址:

http://www.jianshu.com/users/511ba5d71aef

从最简单的Demo开始

我知道,一开始就丢出一堆代码给别人看,是一件很无趣的事儿,但这是必不可少的,因为我需要你先了解我所用的Demo,才能基于这个Demo进行探讨。所以,熟悉下这个再简单不过的Demo吧。

  • AS中新建一个项目,开启DataBinding:

app ——> build.gradle:android {
   ...    dataBinding {        enabled = true    }
   ...
}
  • 项目中用到的数据类User:

public class User {
   private String name ;
   
   public User(String name) {
       this.name = name ;    }
   
   public String getName() {
       return name;    }
   
   public void setName(String name) {
       this.name = name;    } }
  • MainActivity的布局如下:


  • MainActivity的代码如下:


在上面的代码中,我们用参数“milter”创建了一个user对象,然后把它设置给了mainActivityBinding,之后,我们给 id 为 changeUserName 的 Button 添加了一个点击监听器,点击它将会调用user 的setName方法将它的name属性改为“Not milter”。

程序运行起来的界面如下所示:

点击按钮milter会变成Not milter吗

请你猜猜上面的问题,无论对错,希望你能够告诉我结果。

正确答案是:NO!

我们肯定要问:WHY?

先来看看milter是怎么显示在id为username的TextView中的。代码 mainActivityBinding.setUser(user) 执行后,DataBinding框架会进行数据与UI的绑定,id 为 username 的 TextView 通过 android:text="@{user.name}" 绑定了user对象的 name属性,所以显示了 milter。

点击按钮后,user 对象的 name属性 变成了 “Not milter”,但是DataBinding框架并不知道这件事儿,所以它不会重新进行数据与UI的绑定,结果就是 username 中显示的还是 milter。

解决问题的关键在于,怎么让 user对象 告诉 DataBinding框架,我的 name属性 改变了,赶紧更新UI吧!

实现milter变成Not milter

为了达到目标,有三件事要做,我把它称为绑定数据“三步曲”

  • 让User类成为一个可被观察的Observable

DataBinding框架提供了一个 android.databinding.Observable 接口,只要让User类实现这个接口,DataBinding框架 就会向我们的 user对象 注册一个监听器,有了这个监听器,当 user对象 的属性发生变化后,它就可以通知 DataBinding框架,DataBinding框架 收到 user对象 的通知后,就会更新UI数据。

但是,实现 Observable接口,相当于让我们自己实现一个观察者模式,还是有点麻烦的,所以DataBinding框架为我们提供了一个 BaseObservable,该类已经实现了Observable接口,所以,我们只要让User类继承它也就自然实现了Observable接口了。

实现了Observable接口的User类如下所示:

  • 确定User类中会发生变化的属性

虽然现在我们的User类只有一个name属性,但现实中,User类还可以有许多属性,比如sex,age,phoneNumber等。我们希望user对象通知DataBinding框架的时候,能够精确一点,比如告诉DataBinding框架,我的name属性变化了,请更新UI中使用我的name属性的View。这样可以大大减轻DataBinding框架的工作量。

假如user对象仅仅告诉DataBinding框架,我的属性发生了变化,请更新UI吧。那么DataBinding框架将不得不把UI中所有使用user对象属性的View更新一遍,显然,更新那些没有变化的属性纯粹是一种浪费。

确定会发生变化的属性非常简单,就是在相应的getter方法上加上@Bindable注解。在我们的Demo中,要给getName方法上面加上@Bindable注解,如下所示:

@Bindable
public
String getName() {
   return name; }

加上这个注解后,DataBinding框架会在BR这个生成类中,为name属性生成一个唯一的标识符,如下所示:

public class BR {
   ...    public static final int name = 1;
   ...
}

这样,当user对象通知DataBinding框架时,可以用BR.name标识自己的name属性。

你可能会问,为什么不把@Bindable注解加到name field上,像下面这样:

@Bindable
private
String name ;

答案在文首推荐的《DataBinding实现原理探析》中,这里不重复解释了。

  • 当属性变化时通知DataBinding框架

现在,当user对象的name属性发生变化后,我们就可以通知DataBinding框架了,显然,应该在setName方法中通知DataBinding框架。如下所示:

public void setName(String name) {
   this.name = name;    notifyPropertyChanged(BR.name); }

notifyPropertyChanged 方法来自 BaseObservable类。

完成了我们的“三步曲”,本节的目的就达到了。点击按钮前后对比如下:

更加简单的方法

上一部分中,仅仅为了让user在自己的name属性发生变化时通知DataBinding框架,我们要做许多工作,又是继承,又是加注解的,确实有点麻烦!

为此,DataBinding框架给我们提供一个简便方法,那就是使用ObservableField。使用它,我们的User类将变成这样:


然后,将按钮的点击监听器中的代码变成这样:

user.name.set("Not milter");

这就可以了,效果和之前是一样一样的。

更进一步,如果属性是基本数据类型,DataBinding框架还提供了专门的属性类:

 ObservableBoolean,
ObservableByte, ObservableChar, ObservableShort, ObservableInt,
ObservableLong,
ObservableFloat, ObservableDouble, ObservableParcelable

再进一步,如果属性是集合,DataBinding框架也提供了专门的类:

ObservableArrayMap
ObservableArrayList

使用 ObservableField 等 ObservableXXX类,可以让我们省去继承BaseObservable、添加注解,通知DataBinding框架等麻烦,但天下没有免费的午餐,代价就是性能会降低,所以只能在少量属性上这样用,如果大量使用,用户体验可能不太好

好了,关于Observable的部分就到这里,接下来是 BindingAdapter

为什么需要BindingAdapter

在上文的例子中,activity_main.xml中的TextView有如下binding表达式:

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

我们来看看DataBinding是怎么处理上面这行代码的。

首先,DataBinding框架会对binding表达式进行求值,具体怎么求值,在我的文章DataBinding实现原理探析中有详细的论述,这里不再重复。

假设现在DataBinding框架已经对binding表达式求完值,值为CharSequence  “milter”。

现在DataBinding框架面对的情况是这样的:要把值为的“milter”的字符串赋值给TextView的命名空间为android的text属性。

上面这句话读起来比较绕,我们把它做成表格来看:


DataBinding如何使用些信息?我们先从最简单的情况说起,假如没有BindingAdapter机制,DataBinding框架会这样做:

  • 忽略命名空间android,也就是说不管属性前面是什么命名空间,android也好,自定义(如app)的也罢,DataBinding框架都不care,它根本不需要这个信息。

  • 根据属性值text和binding表达式的值CharSequence “milter”在TextView中寻找有如下签名的方法:setText(CharSequence text)

  • 在id为username的TextView上调用:setText("milter")

  • 结束!

上面的过程看起来非常理想对不对?毕竟我们自己也经常调用setText方法。但是由于databinding表达式的存在,事情开始变得复杂。

在我们的例子中,databinding表达式最后算出来的值是“milter”,可在实际情况中,这个表达式的值有很多变数,比如它可能是null,可能与TextView已有的text一样 等等,这些情况下,我们完全没有必要调用TextView的setText方法。

要知道,setText方法是一个复杂耗时间的操作,尤其是如果它的参数是一个Spanned类型,操作会更复杂。

怎么解决这个问题?最好的方法就是当DataBinding框架算出binding表达式的值之后,能够让我们介入,让我们根据求出的值的情形来决定是否调用TextView的setText。

DataBinding框架确实给我们提供了这样的介入机制,这就是 BindingAdapter

使用BindingAdapter

继续我们上面的例子,为了实现我们上面的介入目的,我们按照DataBinding框架的要求,定义出下面的BindingAdapter:


要理解这个BindingAdapter,有三大关键点:

  • 注解@BindingAdapter的参数“android:text”

  • 方法第一个参数TextView

  • 方法第二个参数CharSequence

DataBinding框架会汇总以上三个信息,进而得出结论:

当在TextView上设置text属性,且设置的值的类型是CharSequence时,就不要直接调用TextView相应的setText方法,而是调用用户定义的这个BindingAdapter方法。

Note: DataBinding框架根本不关心text属性前面的命名空间是什么,也不关心这个BindingAdapter的方法名字是什么,我们把它定义成setText,纯属巧合  :)。

在这个BindingAdapter中,我们先是判断是否有必要调用TextView的setText方法,确认有必要后,我们才调用TextView的setText方法,对于没有必要的情况,我们选择直接返回。关于这个BindingAdapter放在哪里,答案是:看你心情,随意放

好了,现在我们来总结一下:

当在任意一个View的任意一个属性上使用binding表达式时,DataBinding框架的处理过程分成三步:

1. 对binding表达式求值

2. 寻找合适的BindingAdapter,如果找到,就调用它的方法

3. 如果没有找到合适的BindingAdapter,就在View上寻找合适的方法调用

现在问题来了,UI控件那么多,它们的属性就更多了,难道对每个需要使用binding表达式的属性,我们都要像上面那样写一个BindingAdapter?有木有想死的冲动?

不用担心,DataBinding框架已经帮我们写好了许许多多的BindingAdapter,覆盖了Android提供的所有控件的绝大多数属性!

告诉你一个秘密,其实上面的BindingAdapter代码,根本不是我写的,而是DataBinding框架已经提供好的。

自定义BindingAdapter

假如我们觉得上面那个系统提供的BindingAdapter不能满足我们的需求,我们想要这个BindingAdapter能够将CharSequence全部变成大写,然后再调用TextView的setText方法,这时,我们就需要自定义BindingAdapter。

DataBinding框架很开明,它承诺:在寻找合适的BindingAdapter时,会优先使用用户定义的BindingAdapter。

现在我们自定义一个我们的BindingAdapter:


这个BindingAdapter其实就比系统提供的BindingAdapter多一句将字符串变成大写的代码,其他完全一样。

有了这个BindingAdapter,任何时候,任何情况下,只要我们在TextView的text属性上使用binding表达式,并且这个表达式的值是CharSequence,那么,我们自定义的BindingAdapter就会被DataBinding框架调用,它会把binding表达式的值变成大写后设置给TextView。如下图所示:

总结:以上,我们深入探讨了DataBinding框架的BindingAdapter机制,从为什么需要它,怎么使用它,怎么自定义它三个方面进行了分析。重点在于理解它的原理。关于BindingAdapter各种好玩的使用方法,请参考官方文档(要科学上网哈!)

https://developer.android.com/topic/libraries/data-binding/index.html

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值