DataBinding难点解析之Observable和BindingAdapter

来源:互联网 发布:数据图表 编辑:程序博客网 时间:2024/05/29 10:23

今日科技快讯

近日,有消息称乐视欲裁员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 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机微信里的外网链接打不开怎么办 微信违规无法加入群聊怎么办 苹果手机微信储存空间不足怎么办 拼多多砍价砍不到0元怎么办 妻子婚内出轨我的熟人怎么办 父亲怀疑母亲有外遇作为儿子怎么办 老婆出轨我总心理过不去坎该怎么办 失业人员离培训机构远了怎么办 遇到申请假冒商品的仅退款怎么办 微信被骗什么消息都不知道怎么办 微信用手机登录显示未注册怎么办 唯品会海淘订单取消不了怎么办 大排量摩托芯片钥匙断了怎么办 夏天太热在宿舍没空调怎么办? 汽车钥匙掉了怎么办半路到不了家 当顾客说产品太贵的时候 怎么办 邮件还没有下载被发送者删除怎么办 绝地求生号被盗了怎么办邮箱没有用 暴雪忘了密码换了手机号怎么办 华为荣耀10看电视缓冲太慢怎么办 交易猫梦幻西游手游恶意仲裁怎么办 银色高跟鞋前面尖尖蹭掉色了怎么办 苹果账号邮箱和密码都忘了怎么办 网易云密码忘记了怎么办手机号换了 超市买过期东西但没有小票怎么办 网购退货商家已收货而不退款怎么办 手机号被别人注册了京东怎么办 中通快递把我发的货弄坏了了怎么办 在淘宝获取不了丰巢柜提货码怎么办 京东快递提货码数手机号错了怎么办 腾讯手游助手不能玩刺激战场怎么办 刚收的京东快递质量有问题怎么办 拼多多买家接受信息没有声音怎么办 苹果6s外放声音有杂音怎么办 华为手机不小心把视频删了怎么办 电脑做完系统不显示声卡了怎么办 苹果手机自带浏览器看视频卡怎么办 pr剪辑的时候视频太卡怎么办 小米手机忘记登录账号密码了怎么办 石膏线条软模硅胶模具气泡多怎么办 京东白条开通失败怎么办金牌用户