最后
分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。
《Java高级面试》
《Java高级架构知识》
《算法知识》
前面的实现都是单向的绑定操作,也就是说数据源改变以后会自动更新到控件上。但是,如果想让控件的值被用户手动输入变化后也能自动地同步到数据源里,这个操作就叫做双向绑定。
双向绑定以后可以实现什么效果?
为了演示一下双向绑定的效果,我给页面上加了一个输入框以及一个文本框,它们都使用的同一个数据源。当用户给输入框删除或者输入新数据以后,这二者虽然使用的同一份数据源,但是由于输入框输入的内容并没有同步到数据源中,所以文本框的数据并不会自动地刷新成用户在输入框中输入的值。
而双向绑定以后是这样的,输入框不管是删除还是输入,都会立即自动把结果同步到文本框上
如何实现双向绑定呢?
双向绑定具体做法是将@{}改成@={}。如下图,在将EditText的值与数据源双向绑定了以后,当用户在输入框中输入内容以后,由于输入框的值会同步到二者共用的数据源中,下面的TextView内容就会自动跟着变化。
4.通过BindingAdapter来自定义Setter
有些控件的属性并不是直接显示在控件上的,而是需要经过处理甚至是第三方API的处理才能使用。又或者是想要覆盖系统自身的属性,也可以,总之就是这里定义的对属性的BindingAdapter注解处理方式会覆盖系统的方式。
比如想要将url设置到ImageView上,需要通过Picasso或者Glide来将url下载转成bitmap来设置,这个时候就需要特殊处理。
首先在Bean里新增一个String类型的url字段,然后在ImageView上自定义一个url属性,将其绑定到url字段上。也可以定义多个,这里多定义了一个error属性。
最后处理一下实际的将url设置到ImageView上的操作
5.设置数据到列表控件中
以RV为例,接下来说一下如何将数据设置到列表控件中,这也是非常常见的操作了。由于列表控件是有Adapter的,相当于设置数据时多传了一层。
这是一个并没有使用DataBinding的简单RecyclerView示例,里面有3种不同布局,当然这里代码写得很不规范,因为这个不是重点。
运行以后大致的效果如下,为了区分三种不同的Item,我将每个Item显示的TextView个数进行了区分,方便大家看懂这是不同的item。
接下来我们要做让它绑定到DataBinding,实现当数据源变化时不调用nofigyDataSetChanged方法来实现Item数据发生变化。
使用步骤参照之前基本使用来即可,第一步新建一个Bean作为DataBinding获取数据库的地方。第二步将那3个item都变成layout包裹的DataBinding布局。
第三步是将当前布局交给DataBinding进行托管
第四步是需要修改一个ViewHolder的实现,由于之前是将View托管到了ViewDataBinding中,所以需要View的时候可以从托管平台DataBinding来获取。这里保存下binding是为了在onBindViewHolder中绑定数据用,要记住现在需要数据都是从ViewDataBiding中获取了,而不能直接从values数组里去获取,不然数据修改了是刷新不了的。这里实现一个接口,是为了偷懒,在onBindViewHolder中统一处理。
最后在onBindViewHolder中调用系统的setVariable方法对DataBinding进行赋值,相比最初的根据position进行判断布局,然后再进行赋值是不是节省了很多代码哈哈,请看下图:
最后,验证一下数据修改时,会自动刷新到rv上吧,我这里开了个线程,每秒修改一下rv上的数据源,注意我只是修改了数据源并没有notifyDataSetChanged。
[图片上传失败…(image-3a755f-1592979137700)]
让我们来看一下最终的效果,符合预期。
上源码(划重点)
1、DataBindingUtil是个什么类?
在使用DataBinding的时候,一般第一件事情是初始化DataBinding,代码大概如下:
final DataBindingTest viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final TestBean testBean = new TestBean();
viewDataBinding.setTestVariable(testBean);
那么DataBinding究竟是何方神圣呢,我们打开该类的属性和方式视图,如下:
这里mapper是调用DataBindingUtil.setContentView初始化DataBinding时用来将layoutid和实际实现类进行映射的一个DataBinderMapperImpl对象,在下面讲解DataBinding初始化的时候还会讲到。sDefaultComponent是预留给开发人员设置的一个属性,默认为null,也会体现在后面DataBinding初始化的过程中。
其中inflate方法是初始化DataBinding方法的另一种方式,在后面给recyclerview上使用DataBinding的例子中会详细讲到,具体代码大概是这样子的:
ViewDataBinding viewDataBinding = DataBindingUtil.inflate(MainActivity2.this.getLayoutInflater(),R.layout.rv_layout1,parent,false);
其中bind()方法也和上面的Inflate方法类似,也是用来初始化DataBindingUtil的方式,和setContentView方法依然类似,这三者功能基本是一样的。
下面的findBinding方法和getBinding方法都是对外提供的static方法,用来返回生成的ViewDataBinding类的,通过这些方法我们可以根据View对象或者布局Id来获取绑定好的ViewDataBinding对象。
convertByIdToString后面也会讲到,是系统提供给我们的对外接口,具体很少使用,看注释是有利于打日志。
bindToAddedViews是调用DataBindingUtil.setContentView()进行绑定的时候会调用的中间方法,目的是为了区分当前是不是只有一个控件,进行一些特殊处理,在讲DataBinding初始化的时候也会讲到。
2、DataBinding初始化的时候都做了什么事情
我们在Activity中使用DataBinding的时候,需要进行初始化,代码大致是这么写的:
DataBindingUtil.setContentView(this, R.layout.activity_main);
我们点进去看下都做了什么操作,可以看到很简单,首先调用了Activity的setContentView方法,这个是不使用DataBinding的时候也需要调用的,其次是调用了bindToAddedViews方法,调用的时候将最外层的Framlayout容器以及布局的LayoutId给传了进去。
在bindToAddedViews方法中,不管走哪个判断分支,是走到了bind方法,其中第一个参数component默认是null,除非咱们手动去设置。第二个参数是所有view的数组,第三个是布局文件Id。
接着往下面看,是调用到了一个Mapper对象的getDataBinder方法
Mapper对象是一个DataBinderMapper抽象类的子对象,这个抽象类代码如下:
实现了DataBinderMapper抽象类的具体子类是DataBinderMapperImpl类,该类的getDataBinder()方法具体代码如下:
ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
switch(layoutId){
case LAYOUT_AAA:
return new AaaBindingImpl(component, view);
case LAYOUT_BBB:
return new BbbBindindImpl(component, view)
}
}
这段代码十分地简单,就是初始化了相应的DataBinding类,没什么好说的,还有就是保存好了所有的布局文件id、Variable名、到一个map中,一直findUseage你会发现最终是在DataBindingUtil类中的convertByIdToString方法使用,该方法是暴露给开发人员的public方法,说明是预留给我们使用的功能,查看方法提示大概是可以用来和日志有关系?
3、setVariable数据更新流程是怎么样的
首先点进去看下,它是ViewDataBinding该抽象类中的抽象方法。ViewDataBinding类是系统生成的抽象类
接下来我们看看它的实现类有哪些,这很明显是每个DataBinding的布局文件生成了一个,命名是以布局文件的首字母大写然后驼峰的方式,最后拼上了BindingImpl,当然这个名字是可以自定义的,注意看第二个实现类名不一样,这是因为我们在布局文件中自定义了其名字。
接下来我们随便点进去一个类吧,查看一下其setVariable方法
发现是调用了setItem1Variable方法,这个方法当然也是自动生成的,是根据variable的name属性生成的。我们可以看到这里首先将数据传给了根据name生成的属性,这就是为了保存起来给后面使用的。
接下来看看从调用notifyPropertyChanged()到最终数据通知到View上的整个流程吧,notifyPropertyChanged方法一直点下去会执行到以下代码:
notifier注册时机是什么?
在这里mNotifier是在setVariable的时候初始化的,下面具体贴一个堆栈就一目了然了 。大家对比一下会发现关于通知类的初始化流程和下面要讲到的callback的注册流程很类似,我后面会手动画一个callback的流程图出来方便大家理解,本文图太多这两个注册的小分支源码就不一步一步地贴图了。
callback注册时机是什么?
在这里callback也是在CallbackRegistry类中添加的,具体也是执行setVariable方法的时候,同时也贴一个堆栈就一目了然了。当然这也是最少要执行一次setVariable的原因之一,如果不执行连回调都没有初始化当然没办法通知。
最后为了方便大家理解这两个注册流程,呈上我给大家画的跳转流程图:
callback注册流程:
明白了mNotifier和callback这两个关键对象的初始化时机,接下来再看看具体是怎么通知的吧。前面看到在notifyPropertyChanged()方法执行时会调用到
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
执行这行代码就相当于执行到了
继续往下面看
发送了一个handler,那handler接收以后做了什么操作呢?
handler执行到的方法是mRebindRunnable.run()方法。
接下来我们看看这个Runnable的具体实现,其定义就在当前类里,具体如下:
里面核心的方法是这个executePendingBindings(),然后是走到了executeBindings()方法
executeBindings()是一个抽象方法,接下来我们看下它的实现,正是系统给我们生成的BindingImpl类,是不是很有亲切感呢!
接下来就简单了,我们看下实现类里究竟做了什么呢?
最后
分布式技术专题+面试解析+相关的手写和学习的笔记pdf
还有更多Java笔记分享如下:
tYWdlcy8xOTk1NjEyNy0yNWIxMWFlNjI0NDcxNDI5LnBuZw?x-oss-process=image/format,png)
executeBindings()是一个抽象方法,接下来我们看下它的实现,正是系统给我们生成的BindingImpl类,是不是很有亲切感呢!
接下来就简单了,我们看下实现类里究竟做了什么呢?
最后
分布式技术专题+面试解析+相关的手写和学习的笔记pdf
还有更多Java笔记分享如下:
[外链图片转存中…(img-HvJXRRTV-1715282485152)]