好像确实如此
刚学Android Jetpack时,前辈们都不怎么推荐使用DataBinding。从中了解到DataBinding是这样的:
- 消除findViewById (我选择kt)
- 在xml中写(逻辑)代码 (黑人问号面???,反感)
- 无需手动设置一些监听 (不就几个监听吗)
soDataBinding在我眼里作用不大,甚至有点反感(主要xml那块),很长一段时间都排斥DataBinding,项目中只使用ViewModel和LiveData等其他Jetpack组件。
渐渐入坑
借助kt的插件,我们在任何地方都不需要写findViewById(感谢大佬指出)。但由于以前不太懂在RecyclerView.ViewHolder中使用kt插件,还是老老实实的findViewById。这也让我想起DataBinding的好处:消除findViewById。而且对一个组件学都没学,在不了解的情况下,就判处"死刑",好像也不妥。
于是我决定尝试学习一下DataBinding,但秉着不在xml中写逻辑代码的原则,在学习DataBinding时,有关运算符的介绍都是跳过不看的。例如这些:
减少胶水代码
原本使用kotlin搬砖的我,减少胶水代码才是databinding为我带来最直接的便利。比起修改LiveData的值,然后设置Observer感知LiveData的变化,才对View的数据或状态进行调整。直接使用DataBinding,修改数据的同时,View的数据或状态同步修改,更有一气呵成的感觉。无论是数据绑定,双向绑定,还是设置监听,都是在减少胶水代码。
正如@却把清梅嗅大佬在Android官方架构组件DataBinding-Ex: 双向绑定篇中总结到的:
DataBinding将烦不胜烦的View层代码抽象为了易于维护的数据状态,同时极大减少了View层向ViewModel层抽象的 胶水代码,这就是最大的优势。
“数据级联”
很多时候,我们需要对数据进行转换,以便在前端给用户展示正确的信息,而这种转换应该是自发的。众所周知,在LiveData中存在map扩展方法(内部调用Transformations#map),我们可以利用该函数对数据进行自发的转换。
//性别
val sex = MutableLiveData()
val sexStr = sex.map {
when(it){
1 -> "男"
2 -> "女"
else -> ""
}
}
我可以照常对sex进行赋值,让数据转换自发进行。而数据绑定时,只需要对sexStr进行绑定或订阅。set原始数据,视图呈现我需要展示的信息,好像这样更符合数据驱动的思想。
但由于当时不知道LiveData也能用于dataBinding的数据绑定(流下了没有技术的眼泪),于是我陷入了使用LiveData无法进行数据绑定,使用Observable*类无法进行自发转换的“困境”。
我在想:Observable对象也具转换的能力,该多好呀。于是我打算利用Observable类#addOnPropertyChangedCallback,对Observable类定义一系列map扩展方法。但定义完一个后发现,Observable类数目较多,对其定义map扩展无疑是 n * n 的排列组合(考虑到避免基础类型的装箱与拆箱)。懒人的我当然是选择放弃啦,只能另寻它法。
直到在官网看到这段代码:
https://developer.android.com/reference/android/databinding/ObservableField
第三个ObservableField的构造方法中,传入了前两个ObservableField对象:first 和 last。并且重载了内部的 get 方法, get 方法的值依据first 和 last生成。这好像是我想要的东西!!
查看了一下源码:
ObservableField.java
/**
* Creates an ObservableField that depends on {@code dependencies}. Typically,
* ObservableFields are passed as dependencies. When any dependency
* notifies changes, this ObservableField also notifies a change.
*
* @param dependencies The Observables that this ObservableField depends on.
*/
public ObservableField(Observable... dependencies) {
super(dependencies);
}
BaseObservableField.java
public BaseObservableField(Observable... dependencies) {
if (dependencies != null && dependencies.length != 0) {
DependencyCallback callback = new DependencyCallback();
for (int i = 0; i < dependencies.length; i++) {
dependencies[i].addOnPropertyChangedCallback(callback);
}
}
}
class DependencyCallback extends Observable.OnPropertyChangedCallback {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
notifyChange();
}
}
简单说就是对构造方法中多个Observable类对象(如 first 和 last)添加一个属性改变回调Observable.OnPropertyChangedCallback。让这些Observable类对象在值改变时(例如first的变化值 或 last的值变化时),通过Observable.OnPropertyChangedCallback回调通知一声(告诉 display:我改变数据了 )。
然后又通过notifyChange()告知其他人,他自身属性也改变了,让其他人重新获取他的值。重写内部的get方法就能够让其他地方重新获取该值时(你都告诉我,你的值改变了,那我当然要重新获取一遍啦),计算出新值。
对于这样的现象:
一个对象拥有感知一个或多个对象的值变化的能力。当其感知对象的值改变时,自动调整自身的值。
暂且叫它“数据级联”吧
解决xml中的逻辑代码
回头一看,那些xml里编写的简单逻辑表达式,不正可以使用“数据级联”进行完成吗?以官网的代码为例:
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
无非就是一个visibility值,需要依赖age的值,进行一些运算得出结果。而且每次age的值更新,需要通知visibility重新进行计算:
val age = ObservableInt()
val visibility = object :ObservableInt(age){
override fun get(): Int {
return if (age.get() > 13)
View.GONE
else
View.VISIBLE
}
}
#xml
android:visibility="@{viewModel.visibility}"
而且以前在xml中无法实现的较为复杂的逻辑代码,也可以尝试通过“数据级联”来实现。这样一来,xml层就只剩下数据绑定和设置监听的代码,我个人觉得还是可以接受的。(问了下前端的同学,Vue好像也是类似DataBinding这样绑定的)
实践
模仿b站的登录页面:
只有手机号码输入框和验证码输入框都存在输入值时,登录按钮才可点击,且透明度跟随改变。
//手机号码 val phone = ObservableField<String>() //验证码 val smsCode = ObservableField<String>()
//登录按钮可点击状态
val loginEnable = object :ObservableBoolean(phone,smsCode) {
override fun get(): Boolean {
//获取手机输入框内容
val phoneStr = phone.get()
//获取验证码输入框内容
val smsCodeStr = smsCode.get()
//手机框和密码框都存在输入值时,才允许点击登录按钮
return if (phoneStr.isNullOrEmpty() || smsCodeStr.isNullOrEmpty())
false
else
true
}
}
//登录按钮的透明度
val loginAlpha = object:ObservableFloat(loginEnable){
override fun get(): Float {
//获取按钮是否可点击的布尔值
val enable = loginEnable.get()
return if (enable)
1.0f
else
0.5f
}
}
此时,我们需要做得就是通过DataBinding将这4个Observable*类对象绑定到xml就可以了(phone和smsCode使用双向绑定)。而这一切,都是自发的,岂不美哉?当然,LiveData也可以使用MediatorLiveData来实现对多个数据源监听。在这些数据源发生改变时,对其进行通知:
//手机号码
val phone = MutableLiveData
//验证码
val smsCode = MutableLiveData
//获取登录按钮可点击状态
val loginEnable = MediatorLiveData
val observer = Observer
refreshEnable()
}
addSource(smsCode,observer)
addSource(phone,observer)
}
//登录按钮的透明度
val loginAlpha = loginEnable.map { enable ->
if (enable)
1.0f
else
0.5f
}
fun refreshEnable(){
//获取验证码
val smsCodeStr = smsCode.value
//获取手机号码
val phoneStr = phone.value
//手机号码和验证码不为空,才可以点击登录按钮
if (phoneStr.isNullOrEmpty() || smsCodeStr.isNullOrEmpty())
loginEnable.value = false
else
loginEnable.value = true
}
## 自定义BindingAdapter优化
DataBinding具有自动优化View更新的能力,这与官方优化的BindingAdapter有不少关系:
#TextViewBindingAdapter.javabr/>@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
//会比较新旧值,一样则不重新赋值。
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
很多时候,我们需要将一些数据绑定的重复逻辑抽离到BindingAdapter中(例如ImageView依据url使用Glide加载图片),但其质量也会存在参差不齐的情况。这需要我们在自定义BindingAdapter时,尽量对值进行一些必要的判断,以减少View的重新测量与重绘。
在BindingAdapter中拦截没用的数据来优化View更新,宛如是“末段拦截”。比起 “末段拦截” 更有用的是 “中程拦截”。具有防抖功能的Observable*类比 LiveData更具备 “中程拦截” 的能力,因为它在每次赋值前都会判断是否和旧值相等:
public void set(int value) {
if (value != mValue) {
mValue = value;
notifyChange();
}
}
### 回传旧值
DataBinding允许在自定义的BindingAdapter中,回传旧值。但是需要先声明旧值,再声明新值。详见:DataBinding介绍。但对于能通过公开属性获取到的数据,要求DataBinding回传旧值的作用并不大。反而像监听、回调这些,可以借助DataBinding回传旧值的功能来进行移除。(当然直接替换的监听/回调也就不需要回传旧值)
@BindingAdapter("android:onLayoutChange")
fun setOnLayoutChangeListener(
view: View,
oldValue: View.OnLayoutChangeListener?,
newValue: View.OnLayoutChangeListener?
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue)
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue)
}
}
}
## 使用DataBinding的一点小建议
* 不在xml中写任何逻辑代码,databinding在xml中只负责数据绑定和设置监听。
* xml标签中使用databinding的属性统一移到标签底部。
* 尽量使用官方定义的BindingAdapter进行数据绑定与设置监听。
### 小结
DataBinding加速MVVM的构建,减少大部分胶水代码。没有DataBinding也可以借助LiveData和ViewModel构建MVVM。如果实在无法容忍在xml中写入额外的东西,可以放弃DataBinding。
xml中的代码应该全部抽离到Java/Kt中,使用"数据级联"将其进行转换,再将其绑定到View中。
数据绑定优先选择Observable*类而非 LiveData。如果想对Observable*类的值进行监听,可以使用Observable*类#addOnPropertyChangedCallback添加回调。
DataBinding总体来说还是很香的^ ^
![](https://upload-images.jianshu.io/upload_images/1095900-997158c25a171eaf?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>作者:[大棋](https://juejin.im/post/5dfa26c9f265da33d7442cdb)
最后其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,**从来都是我们去适应环境,而不是环境来适应我们!**
这里附上上述的技术体系图相关的几十套**腾讯、头条、阿里、美团等公司19年的面试题**,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含**知识脉络 + 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。
**相信它会给大家带来很多收获:**
![](https://upload-images.jianshu.io/upload_images/1095900-2bb33f07f71bcbd9.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
![](https://upload-images.jianshu.io/upload_images/1095900-18aa27e236c3d6b5.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/788/format/webp)
**上述【高清技术脑图】以及【配套的架构技术PDF】可以 加我wx:X1524478394 免费获取!**
> 当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。