1. 目录
2.DataBinding的疑惑
假设你已经会用databinding,不会就去看一下怎么用,很简单,
- 下面是新建一个xml,名字叫layout_test1根布局是layout,你重新编译一下项目之后系统就会生成LayoutTest1Binding,以你的xml的名字加binding驼峰命名
- AgeData类两个属性,一个名字,一个年龄。
如上图所示,我们只要按照DataBinding规定的写法,我们通过获取到的引用就能拿到布局里面的控件,设置完数据,就能更新页面。
我们平常页面更新的三个流程:
- 在onCreate生命周期方法里面通过setContentView设置页面布局
- 通过findViewById获取到控件的引用
- 通过控件的引用调用对应的方法TextView的就是setText方法去更新
到DataBinding这里怎么就什么都不需要做了,就直接可以用了呢?
欢迎大家步入DataBinding的神奇世界,我来为大家一一解惑。
3.设置页面布局
这个其实最简单,我们先来看一下它的用法,调用的一个工具类的setContentView方法,传递了一个泛型,如下代码:
1 | val binding = |
那么,为什么按照它这个调用方式就能设置布局呢?我们来看一下它的setContentView方法:
1 | //我们调用的这个方法,它又调用的一个setContentView重载方法 |
上面,我们看到了实际上,他还是调用的activity的setContentView方法去设置的布局。
4.获取view引用
我们平时设置布局,findViewById找到控件,然后通过调用控件的方法去设置内容,从而达到更新页面的功能。那么,databinding是怎么获取到控件的呢?我们接着上面的方法往下看:
1 | //这里的parent就是我们的内容布局,也就是我们写的布局的上一层 |
我们再来看看这里绑定的方法,这里调用的方法其实都是差不多的,都是重载方法,一个是传view,一个是传的view类型的数据。
1 | static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots, |
我们点进去发现跳转到了一个抽象类的方法。然后,怎么办呢?方法调用,不是静态的,肯定得看对象呀。静态的是类.方法名,不是静态的,变量.方法名。我们回过头来看这个调用的对象的类型。
1 | public class DataBindingUtil { |
回到最开始的位置sMapper.getDataBinder方法,其中sMapper是DataBinderMapperImpl类型的,但是,它的类里面并没有getDataBinder方法,所以,这里调用的就是DataBinderMapperImpl父类的getDataBinder方法。也就是MergedDataBinderMapper。我们来看一下
1 | public class MergedDataBinderMapper extends DataBinderMapper { |
没错,就是这里上面构造方法里面添加的addMapper(new com.haichenyi.hcy.DataBinderMapperImpl()),我们再来看看这个DataBinderMapperImpl,看看它的getDataBinder,因为前面sMapper.getDataBinder调用的就是它
1 | //简单的贴我们需要的 |
到这里,我们来聊一下,为什么他这里获取到的tag有值呢?项目重新编译的时候,databinding会根据xml的根布局是否是layout布局来重新生成xml。如下图:
如上图所示,databinding传进来的layout其实就是这个layout,并不是我们自己写的布局。所以,这个位置getTag能获取到值。
1 | <?xml version="1.0" encoding="utf-8"?> |
对比一下我们自己的布局:就会发现规律
- 在我们写的根布局会加上tag,名字叫:layout/(你的layout)_0
- 在你有用bean类属性(@{ageData.name}之类)的位置,他会给你的view加上tag,名字叫:binding_i,这个i,从1开始,依次+1
结合总结的这两条规律,对比一下上面的两个。
我们再返回到上面,switch语句里面,就返回了LayoutTest1BindingImpl类,这个是不是很眼熟?没错,我们调用databinding的时候传递了一个泛型LayoutTest1Binding,他俩什么关系呢?Impl结尾,一看就知道是实现类。我们点到LayoutTest1BindingImpl看一下。
我们从前面获取到布局的实现类LayoutTest1BindingImpl,上面的流程就走完了,我们还是没有看到怎么获取view的方法,一个类的创建先走的它自己的构造方法,那我们就来看一下这个实现类构造方法里面是什么,或许就有答案了。
1 | public class LayoutTest1BindingImpl extends LayoutTest1Binding { |
重点就在这里了,mapBindings这个方法里面就是获取view的流程,我们,可以来看一下。
1 | protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root, |
经过上面这个方法的调用,就把布局里面定义了id的,生成了tag的view放进了binding里面,Object类型的数组返回回去。如下图所示:
从上面循环也看出来了,优先通过tag找view,然后通过findViewById找view,两个都没有,就被丢弃了。所以,这里只有5个。我们布局一共7个view;LinearLayout和其中一个ImageView没有id,没有tag。循环的时候就没有找到。
再强调一遍DataBinding生成tag,是因为你再xml里面通过它规定的方式设置了值,即使,你没有给view设置id,这里也会绑定上,因为,它优先通过tag绑定view的。
流程走到这里,DataBinding已经把需要的view都找到了,并且放到了Object数组里面,接下来就是绑定引用了。我们再回过头去看重载的构造方法的代码:
1 | public LayoutTest1BindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { |
就是这个类,点进去看
1 | public abstract class LayoutTest1Binding extends ViewDataBinding { |
这里只贴出来了几个我们想要知道的东西,我们从前面通过tag,id把view找到之后,传递到构造方法里面,然后,这里创建的全局的变量,分别赋值给这些变量。这样,就拿到了这些引用了。
一直到这里,获取view的引用,绑定流程就走完了。再回过头去看最开始的问题binding.tvName,binding.tvAge等等,就是拿的这里的引用,也可以看到我们最开始拿到的binding这个变量的类型就是ViewDataBinding,我们这里的LayoutTest1Binding继承的就是ViewDataBinding,就全部串起来了。
5.更新(界面)流程
可以先说结论,用的就是观察者模式。绑定view的后面它他先注册监听,我们设置数据的时候,触发回调,更新界面。
文章有点长了,更新流程放到下一章吧。这个view的绑定东西还是比较长的。最后总结一下绑定view的流程图