- 双向绑定
- DataBinding剩下的注解
很快啊,就来到了DataBinding的第三篇。在这里将介绍DataBinding中高级的知识点(自认为高级),下面就来瞧瞧这些高级的知识点。
1、双向绑定
其实早该在介绍绑定表达式的时候应该要介绍双向绑定的了,但是为了双向绑定完整,所以就留到了这,所谓双向绑定是这样的:
我们可以单向绑定数据和一些监听器(事件)。在更新数据的时候,绑定了的控件就会更新,而监听器发生改变的时候,我们就更新绑定的数据。
这就是双向绑定的原理。
而DataBinding给我们提供了双向绑定的基本实现以及自定义。
1.1、可观察数据类型
并不是所有的数据类型都可以进行双向绑定,DataBinding给我们提供了一些:
大概是这么多。
而使用双向绑定的表达式也和普通绑定表达式有那么一点点不一样:
@={}
这就是双向绑定表达式,比普通绑定表达式多了一个等号=
。
最常用到双向表达式的莫过于EditText
控件了,当输入内容时,更新绑定的数据,但改变数据时,更新EditText
控件的显示。
1.2、双向绑定特性
也并不是所有的属性都支持双向绑定,至于哪些属性(特性)可以进行双向绑定,官方也列出了一个表格:
1.3、自定义可观察数据类型(@Bindable)
如果DataBinding提供的可观察数据类型都不能够满足你的业务,那么还是可以自定义的。
新建一个类,继承BaseObservable
,并创建一个属性,给这个属性加上@Bindable
注解,然后重写该属性set
方法即可,大概是这样的:
class Demo : BaseObservable() {
@Bindable
var name: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
}
@Bindable
注解:该注解主要作用于get方法或者是字段本身,作用是生成一个BR值供notifyPropertyChanged
方法调用以更新数据。
这时我们就可以对name这个属性进行双向绑定了:
<EditText
android:text=“@={demo.name}”/>
但是你实际运行你会发现它报错了,报错的原因是类型不匹配。这时候该怎么办呢?
1.4、双向数据转换(@InverseMethod)
Databinding提供了数据转换的方案,单向和双向都有,我们先来说双向的。
定义一个静态类,提供两个方法。一个充当正向数据转换器,另外一个充当反向数据转换器,大概是这样的:
object Canversion{
@InverseMethod("chToString")
fun stringToCh(value: String): CharSequence {
return value
}
fun chToString(value: CharSequence): String {
return value.toString()
}
}
事实上,两个顶级函数也是可以的。
InverseMethod
注解:该注解修饰的就是正向转换器,而InverseMethod
接收一个参数,就是反向数据转换器的名称。
还记得<import>
标签吗,当转换器定义好之后,我们就可以通过该标签将这个类导入到xml中,然后使用正向转换器就可以了:
<import type=“…Conversion”/>
<EditText
android:text=“@={Conversion.stringToCh(demo.name)}”/>
1.5、自定义双向绑定(InverseBindingAdapter)
如果DataBinding默认实现的双向绑定特性也不能满足需求的时候,就只能自定义了。
事实上,双向绑定就由两个单向绑定(数据绑定和监听器绑定)组成的,所以第一步就是建立两个单向绑定。下面重新实现android:text
的双向绑定。
首先创建一个数据绑定:
@BindingAdapter("app:test")
fun test(view: TextView, text: CharSequence?) {
val oldText = view.text
if (text == oldText || (oldText.isNullOrEmpty())) {
return
}
if (text is Spanned) {
if (text == 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.text = text
}
在赋值之前需要对数据进行判断:
只有是更新的数据才可以被赋值。防止无限循环的发生。
然后,创建一个事件监听绑定:
@BindingAdapter("app:testAttrChanged")
fun setTextWatcher(view: TextView, textAttrChanged: InverseBindingListener?) {
val newValue: TextWatcher? = if (textAttrChanged == null) {
null
} else {
object : TextWatcher {
//code..
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
textAttrChanged.onChange()
}
//code..
}
}
if (newValue != null) {
view.addTextChangedListener(newValue)
}
}
这是一个事件监听,当EditText发生改变的时候,就会通过InverseBindingListener
对象通知更改。
最后,将这两个绑定关联在一起:
@InverseBindingAdapter(attribute = "app:test", event = "app:testAttrChanged")
fun getTextString(view: TextView): CharSequence {
return view.text // 返回控件的值
}
InverseBindingAdapter
注解:该注解主要有两个参数,attribute
指定数据绑定的名称,event
指定事件绑定的名称。作用是将两者结合起来形成双向绑定,并且响应事件绑定,在事件发生时返回控件的值。
通过这三步,自定义双向绑定的属性就完成了:
<EditText
app:test=“@={demo.name}”/>
自定义双向绑定的一个细节:
在自定义双向绑定中,对于app:testAttrChanged
是可以识别的,规律就是:
数据绑定名称+AttrChanged。
所以在关联的时候event
可以省略不写。
1.6、简化自定义双向绑定(InverseBindingMethods和InverseBindingMethod)
有没有更加简单的方式实现双向绑定呢?答案是没有的!!双向绑定就必须是一个数据绑定加上一个事件绑定。这是永远都没办法省略的。但是如果你有多个双向绑定需要实现,那么可以简化的只有InverseBindingAdapter
注解修饰的方法了。
首先定义一个类:
@InverseBindingMethods(
InverseBindingMethod(
type = TextView::class,
attribute = "app:test",
method = "getText"
)
)
object BindingAdapterImpl
InverseBindingMethods
:该注解接收的参数是InverseBindingMethod
数组。
而一个InverseBindingMethod
对应的就是一个InverseBindingAdapter
,下面我们来分析一下InverseBindingMethod
也就是说InverseBindingMethods
和InverseBindingMethod
的作用就是批量的InverseBindingAdapter
。
InverseBindingMethod
该注解有四个参数:
type
:是一个class对象,指定双向绑定的控件,比如TextView
attribute
:和InverseBindingAdapter
中同名的参数作用一样,指定数据绑定的方法。
event
:和InverseBindingAdapter
中同名的参数作用一样,指定事件绑定的方法。(可以看出,上面的例子我没有写该属性,是因为可以自动推测)
method
:指定控件提供数据的方法。(事实上该参数也可以不写,当控件存在与attribute
同名的get方法时,也就是说如果TextView中存在一个getTest
方法,那么这个参数就可以缺省)
需要注意的是:这两个注解替代的是InverseBindingAdapter
,所以数据绑定和事件绑定依旧需要实现。
一句话概括:
当可观察数据对象更改时,通过attribute
指定的方法更新type
的对象,当event
事件发生时,将从method
方法中获得数据返回给可观察数据对象。
2、DataBinding剩下的注解
终于说完双向绑定了,在说明双向绑定的时候也将和双向绑定有关的注解说了一下,接下来就说说DataBinding剩下的注解。
2.1、单向数据转换(BindingConversion)
上面提到数据转换有两种,双向的已经介绍了,现在来看单向的数据转换:
@BindingConversion
fun dateToString(time: Date): String{
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time)
}
将Date类型转换成String类型输出。
<TextView
android:text=“@{date}”/>//date是一个Date类型的对象
注意:
单向数据转换只需要输入(参数列表)输出(返回值)对上即可自动使用,无需手动调用。
再次注意:所有的数据转换都只能一对一。
2.2、简化单向绑定(BindingMethods和BindingMethod)
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask")
})
和双向绑定的差不多,BindingMethods
接收的也是BindingMethod
的数组。
来看看BindingMethod
注解:
@BindingMethod
注解有三个变量:
type
:指定单向绑定的控件,比如:TextView
attribute
:指定单向绑定的名称
method
:指定响应单向绑定的set方法
BindingMethods
和BindingMethod
相当于批量的BindingAdapter
。
一句话概括:
attribute
属性实现单向绑定,通过method
方法将值设置给type
类型的控件。
总结
最后一篇介绍了dataBinding的双向绑定和剩下的注解,到此Databinding算是基本讲完了。DataBinding主要分为三部分:xml部分,activity/fragment部分以及自定义绑定(单向绑定和双向绑定)部分。
到后面你会发现,xml之所以可以使用绑定表达式,是因为@BindingAdapter标签。当然了,这只是DataBinding的使用,真正的实现:为什么@BindingAdapter这么神奇,其背后的原理才是DataBinding真正的核心。这就是注解部分的内容了。对于日常开发来说,学完DataBinding的这几个注解就已经足够了。
对于学习,最重要的是要去写,只有真正写过,实现了,才能算掌握了。
因为真理都是在实践当中。