DataBinding使用教程(三):各个注解详解
有哪些注解
- @Bindable
- @BindingAdapter
- @BindingBuildInfo
- @BindingConversion
- @BindingMethod
- @BindingMethods
- @InverseBindingAdapter
- @InverseBindingMethod
- @InverseBindingMethods
- @InverseMethod
- @Untaggable
以上就是DataBinding中所有的注解,一共11个注解,其中@BindingBuildInfo与@Untaggable这两个注解是hide的,除了这两个之外,其他9个注解在我们日常开发中都有可能用到,但是最常用的只有如下2个注解:
- @Bindable
- @BindingAdapter
本文会讲解所有的注解,而@BindingAdapter注解的使用又相对较难,所以会着重讲解,其他注解也都会有不同深度的讲解。
1. @BindingBuildInfo
作用:在dataBinding生成相关代码时,该注解用来生成相关的databinding信息,信息中包含一个buildId字段。
在源码中也能找到BindingBuildInfo类的身影:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.@Untaggable
作用:在dataBinding中,通过为view设置tag的方式来标记一个view,然后dataBinding生成代码时,会根据这些tag找到对应的view,然后设置相关的属性。@Untaggable注解有一个String数组,用来存储相关的viewType,在生成相关代码时,dataBinding会从该数组中查找viewType,判断该view是否可设置tag。
难道还有DataBinding不支持的操作?那必须的,在fragment标签中就不支持dataBinding相关的开发方式,一用就会抛异常的,自然不能给fragment这种viewType设置tag。
3.@BindingConversion
- 作用于方法
- 被该注解标记的方法,被视为dataBinding的转换方法。
- 方法必须为公共静态(public static)方法,且有且只能有1个参数
问:那到底什么属性要被转换?又要把属性转换成什么呢?
答:举个例子,比如View的android:background=""
属性,属性值为drawable类型的对象,如果你要放置一个@color/black
的属性值,在不使用dataBinding的情况下没有任何问题,但是如果你要使用了dataBinding方式开发,则会报错,原因在于android:background=""
的目标值类型为drawable,而databinding会把@color/black
先解析成int类型的颜色值,这时如果把int类型的颜色值直接赋值到目标为drawable类型的参数中去,那绝逼是要报错的,如果我们可以在int类型的颜色值赋值之前,让int类型的颜色值自动转换为colorDrawable对象,就可以解决这个问题,而@BindingConversion注解就是干这个事的。
我们先来看一个官网上的示例:
- 1
- 2
- 3
- 4
如果只有上面那段代码,那肯定会报错的,原因上面也说了,所以我们需要定义一个转换方法,把整型的颜色值转换为drawable对象:
- 1
- 2
- 3
- 4
这时再运行就不会出错了,因为在int型颜色值赋值之前已经被自动转换为ColorDrawable了。
肯定有人会有疑问:
- 为什么我没有定义convertColorToDrawable这样的方法,直接写完布局文件就运行也没有报错呢?
- convertColorToDrawable这个方法是什么时机被调用的呢?
首先之所以没有报错的原因,其实是因为convertColorToDrawable这个方法在dataBinding的jar包中已经帮我们定义好了,我们不用再定义了,所以不会报错。源码路径为:android.databinding.adapters.Converters
。
下面再说说调用时机:Android中的每个xml中的属性其实都对应着相应的java方法的,如果在xml中设置的属性值的类型与对应的Java方法的参数类型不符,这时 dataBinding就会去寻找可以让属性值转换为正确类型的方法,而寻找的根据就是所有被@BindingConversion注解标记的方法,这时convertColorToDrawable方法就会被调用了。
如果我们自己定义了一个功能相同的convertColorToDrawable方法,那么dataBinding会优先使用我们自己定义的方法。
如果我们自己定义了多个功能相同的convertColorToDrawable方法,比如convertColorToDrawable01,convertColorToDrawable02,convertColorToDrawable03,dataBinding会选择顺序靠后的方法去使用,说起来很抽象,举个例子:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如上代码所示,convertColorToDrawable03方法排在最后面,所以dataBinding会选择convertColorToDrawable03去使用。
但是个人强烈建议不要定义功能重复的方法,你这不是闲的蛋疼吗
但是话说回来,如果我们真的需要定义多个参数类型相同,返回值类型也相同但是方法体中的实现却各不相同的方法,我们希望dataBinding能分辨出方法之间的不同,但是很不幸的是,至少目前来说,dataBinding是看不出这些方法的区别的,所以还是会选择顺序靠后的那个方法,即便我们真正想让它调用的是其他方法,所以这也算是DataBinding的一个不足之处吧。
4.@BindingMethod与@BindingMethods
- @BindingMethods注解一般用于标记类
- @BindingMethod注解需要与@BindingMethods注解结合使用才能发挥其功效
- 用法极其简单,但是使用场景很少(因为大多数场景,dataBinding已经帮我们做好了)
问:@BindingMethod与@BindingMethods存在的意义?
答:为了说明@BindingMethod与@BindingMethods存在的意义,我强行写了一个例子:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在android:gravity="@{Gravity.CENTER}"
属性中我使用了dataBinding表达式设置文本对齐方式,在编译时,dataBinding会根据属性名gravity
到TextView源码中寻找对应的setGravity()
方法,然后把我们的Gravity.CENTER
值设置进去。生成的代码如下:
- 1
- 2
因为TextView中存在目标对象setGravity()
且参数类型和返回值类型都一样,所以生成的代码没有任何问题。
但是如果找不到对应的set方法,那么编译器就会报错,告诉我们在某个View中找不到相关方法,举个官网上的例子:android:tint
属性实际对应的方法为setImageTintList()
,而不是setTint()
,先看如下代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
可以看到,在android:tint
属性中,我们使用了dataBinding表达式,如果不做任何处理,那么dataBinding在编译时就会报错,因为在ImageView中找不到对应的setTint()方法,android:tint
属性对应的应该是setImageTintList()
,所以为了让程序能正常运行,我们需要让dataBinding能寻找指定的set方法来生成代码,即使用setImageTintList()
方法而不是setTint()
方法;而@BindingMethod与@BindingMethods就是用来指定某个xml属性对应的set方法的。这也就是@BindingMethod与@BindingMethods注解存在的意义。
ps:可能会有人说,我没有做任何处理,直接使用
android:tint="@{@color/colorAccent}"
也没有报错啊,其实是因为dataBinding已经帮我们定义好了转换方法了,在你使用android:tint="@{@color/colorAccent}"
时,已经帮你自动使用setImageTintList()
方法生成代码了。
说完了@BindingMethod与@BindingMethods存在的意义,那么@BindingMethod与@BindingMethods到底怎么使用呢?
@BindingMethod
有3个字段,这3个字段都是必填项,少一个都不行:
- type:要操作的属性属于哪个View类,类型为class对象,比如:ImageView.class
- attribute:xml属性,类型为String ,比如:”android:tint”
- method:指定xml属性对应的set方法,类型为String,比如:”setImageTintList”
组合起来如下:
- 1
作用就是:在为ImageView的android:tint属性生成代码时,使用setImageTintList()方法生成。
@BindingMethods
因为@BindingMethod注解不能单独使用,必须要结合@BindingMethods才能发挥其功效,所以@BindingMethods注解其实就是一个容器,它内部有一个BindingMethod数组,存放的是一个一个的BindingMethod。
@BindingMethod与@BindingMethods结合起来使用的代码示例如下:
- 1
- 2
- 3
- 4
- 5
这样就完了吗?对,没错, @BindingMethod与@BindingMethods就是这样用的,是不是很简单?定义完以上的代码之后,在编译时,如果你使用到了上面指定的某个属性,且在属性值中使用了dataBinding表达式,那么在生成代码时,dataBinding会自动选择指定的方法去生成。
ImageViewBindingAdapter路径为:android.databinding.adapters.ImageViewBindingAdapter
5.@Bindable
- 该注解用于双向绑定,需要与 notifyPropertyChanged()方法结合使用
- 该注解用于标记实体类中的get方法或“is”开头的方法,且实体类必须继承BaseObserable.
- 用法极其简单
实体类也可以不用继承BaseObservable,而是实现Observable接口,但是需要自行处理一些接口方法逻辑,BaseObservable是实现Observable接口的类,内部已经做好了相关逻辑处理,所以选择继承BaseObservable相对简单一些。
示例用法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
既然用法如此简单,那就不多说了。
@Bindable注解是用来干什么的?
使用@Bindable注解标记的get方法,在编译时,会在BR类中生成对应的字段,然后与notifyPropertyChanged()方法配合使用,当该字段中的数据被修改时,dataBinding会自动刷新对应view的数据,而不用我们在拿到新数据后重新把数据在setText()一遍,就凭这一点,dataBinding就可以简化大量的代码.具体在代码中如何使用双向绑定,在下一篇文章【DataBinding使用教程(四):BaseObservable与双向绑定】会有详细的使用示例,这里就不赘述了。
6.@BindingAdapter
- 用于标记方法,方法必须为公共静态方法
- 方法的第一个参数的类型必须为View类型,不然报错
- 用来自定义view的任意属性
- 1
- 2
- 3
- 4
- 5
上面是源码中@BindingAdapter
注解的定义,可以看到:
- value属性是一个String数组,用来存放自定义的属性,示例:android:onItemClick
,app:onItemClick
- requireAll是一个布尔值,用来表示定义的所有属性是否必须都要使用。
完整示例:
- 1
在上面的代码中,我们定义了3个属性,requireAll=false
代表我们在使用时,可以只使用其中一个属性,也可以三个属性都使用;如果requireAll=true
,代表我们定义的这三个属性都是必须要使用的,不然就会报错。
我们知道,默认情况下,如果某个View没有android:xxx=""
或者app:xxx=""
属性,那么我们在布局文件中是使用不了这些属性的,因为我们根本没有定义这些属性,所以使用不了。但在dataBinding的世界中,即便我们没有定义这些属性,我们依然可以通过android:xxx=""
或者app:xxx=""
的方式使用这些属性,只要对应的java方法中有setXxx()
方法即可。说起来可能很抽象,我们举个例子:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
在TextView中,只有setMovementMethod()
方法,而没有android:movementMethod=""
属性,但从上面的代码可以看到,我们通过app:movementMethod=""
的方式可以直接使用movementMethod
属性,之所以可以这么做,是因为dataBinding支持这么做,这也是dataBinding的特性之一,在dataBinding中,在编译时,dataBinding会根据属性名movementMethod
从TextView源码中寻找setMovementMethod()方法,并放入对应的参数,而在TextView中正好存在setMovementMethod()方法且参数类型也一样,这样就实现了即便android默认没有提供相关属性,但是只要有相关set方法,我们就可以使用对应属性,这个特性在上面说到@BindingMethod与@BindingMethods
时也提到过,是不是很爽很刺激呢?
如果你感觉还不够刺激,我们来个更刺激的:
- 如果一个view既没有android:xxx=""
或者app:xxx=""
属性,也没有setXxx()
方法,我们通过@BindingAdapter
同样可以实现自定义android:xxx=""
或者app:xxx=""
属性,然后使用。
如果你感觉还是不够刺激,我们还有终极大招:
- 如果我们要使用其他类中的方法,这些方法肯定是不属于这个View的,我们可以通过@BindingAdapter
自定义一个或一些属性,让我们可以在这个view中使用属性来操作其他类中的方法。一个最典型的使用场景就是RecyclerView与adapter,通过@BindingAdapter
,我们可以把adapter中的方法直接配置到RecyclerView的属性中去,就像这样:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
理论总是枯燥的,下面通过2个示例来演示如何使用@BindAdapter
。
示例一:
为ImageView设置url,placeHolder,errorDrawable
1. 使用@BindingAdapter
定义相关属性:
这里要注意的是:定义的属性的顺序与方法中参数的顺序必须保持一致,且方法中第一个参数必须是要操作的View。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 使用自定义的属性
使用自定义属性的时候,对顺序没有要求,只要保证传入的值的类型与loadImage()
方法中对应参数的类型一致即可。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
示例二:
为RecyclerView设置adapter,这里adapter使用BaseRecyclerViewAdapterHelper,一方面,主要是BaseRecyclerViewAdapterHelper可以额外设置一些方法,比如:setOnItemClickListener
,setOnLoadMoreListener
,setEnableLoadMore
等,在演示@BindingAdapter
用法时能更加说明问题;另一方面,也想凸显出简化代码的幅度,当BaseRecyclerViewAdapterHelper结合DataBinding之后,你会发现写adapter变得更加简单,就像下面这样:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 使用
@BindingAdapter
定义相关属性:
我们针对setOnItemClickListener
,setOnLoadMoreListener
,setEnableLoadMore
这三个方法定义三个属性:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 使用自定义的属性
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
通过上面的方式,我们就实现了通过在RecyclerView中配置属性达到为adapter设置点击监听,上拉加载监听,以及是否开启监听的目的。
其中的
app:adapter="@{adapter}"
是因为RecyclerView有setAdapter方法,结合databinding的特性,故而可以这样写。而app:layoutManager="LinearLayoutManager"
属性是RecyclerView自己提供的一个属性,为了方便我们为RecyclerView设置layoutManager,其内部采用反射构造一个目标layoutManager,然后通过RecyclerView的public void setLayoutManager(LayoutManager layout)
再进行设置。
自定义的属性是如何被调用的
我们以ImageView设置url,placeHolder,errorDrawable为例进行说明。把代码粘过来,防止大家再拉回去看代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在编译时,dataBinding扫描ImageView中使用dataBinding表达式的属性【扫描的依据是属性值是不是以@{
开头的】,然后找到了android:placeHolder="@{@drawable/placeholder}"
,android:imageUrl="@{url}"
,android:error="@{@drawable/error}"
这3个属性,然后判断这3个属性是不是ImageView的自有属性,如果是自有属性,也会去找对应的set方法生成代码;如果不是自有属性,会先找有没有对应且合适的set方法,有的话,就用set方法生成代码,没有的话,就会从所有定义的BindingAdapter中去找合适的方法,显然,dataBinding会找到这段代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
找到之后,就会调用我们自定义的loadImage方法,然后把相关参数放进去,这里就要求我们定义的属性与方法中的参数的顺序必须一致,这样dataBinding在生成代码时就知道android:imageUrl
的属性值应该放到参数的第二个位置,android:placeHolder
属性值应该放到参数的第三个位置,android:error
属性值应该放到参数的第四个位置。
dataBinding生成的代码如下所示:
- 1
- 2
- 3
- 4
- 5
7.@InverseBindingAdapter
- 作用于方法,方法须为公共静态方法。
- 方法的第一个参数必须为View类型,如TextView等
- 用于双向绑定
- 需要与@BindingAdapter配合使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- attribute:String类型,必填,表示当值发生变化时,要从哪个属性中检索这个变化的值,示例:
"android:text"
- event: String类型,非必填;如果填写,则使用填写的内容作为event的值;如果不填,在编译时会根据attribute的属性名再加上后缀“AttrChanged”生成一个新的属性作为event的值,举个例子:attribute属性的值为”android:text”,那么默认会在”android:text”后面追加”AttrChanged”字符串,生成”android:textAttrChanged”字符串作为event的值.
- event属性的作用: 当View的值发生改变时用来通知dataBinding值已经发生改变了。开发者一般需要使用
@BindingAdapter
创建对应属性来响应这种改变。
@InverseBindingAdapter的简单示例
- 1
- 2
- 3
- 4
关于什么叫双向绑定以及@InverseBindingAdapter更加深入的用法,在下一篇文章【DataBinding使用教程(四):BaseObservable与双向绑定】中会有详细的说明,这里就不赘述了。
8.@InverseBindingMethod与@InverseBindingMethods
- @InverseBindingMethods注解用于标记类
- @InverseBindingMethod注解需要与@InverseBindingMethods注解结合使用才能发挥其功效
- 用法极其简单
- 用于双向绑定
- @InverseBindingMethods需要与@BindingAdapter配合使用才能发挥功效
用法示例:
- 1
- 2
- 3
- 4
@InverseBindingMethod
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- type:Class类型,必填,如:SeekBar.class
- attribute:String类型,必填,如:android:progress
- event:String类型,非必填,属性值的生成规则以及作用和@InverseBindingAdapter中的event一样。
- method:String类型,非必填,对于什么时候填什么时候不填,这里举个例子说明:比如SeekBar,它有
android:progress
属性,也有getProgress
方法【你没看错,就是getProgress
,不是setProgress
】,所以对于SeekBar的android:progress
属性,不需要明确指定method,因为不指定method时,默认的生成规则就是前缀“get”加上属性名,组合起来就是getProgress
,而刚才也说了,getProgress
方法在seekBar中是存在的,所以不用指定method也可以,但是如果默认生成的方法getXxx
在SeekBar中不存在,而是其他方法比如getRealXxx
,那么我们就需要通过method属性,指明android:xxx
对应的get方法是getRealXxx
,这样dataBinding在生成代码时,就使用getRealXxx
生成代码了;从宏观上来看,@InverseBindingMethod
的method属性的生成规则与@BindingMethod
的method属性的生成规则其实是类似的。
关于@InverseBindingMethod与@InverseBindingMethods更加具体的用法在下一篇文章【DataBinding使用教程(四):BaseObservable与双向绑定】中会有说明,这里同样就不再赘述了。
9.@InverseMethod
- 作用于方法,
- 用于双向绑定
- 用法较简单
@InverseMethod
注解是一个相对独立的注解,不需要其他注解的配合就可以发挥它强大的作用,它的作用就是为某个方法指定一个相反的方法。它有一个String类型的必填属性:value,用来存放与当前方法对应的相反方法。
正方法与反方法的要求:
- 正方法与反方法的参数数量必须相同
- 正方法的最终参数的类型与反方法的返回值必须相同
- 正方法的返回值类型必须与反方法的最终参数类型相同。
举个例子大家应该就明白了:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
@InverseMethod注解在一些特殊的业务场景中,可以大大简化我们的代码,简直爽的不行,在下一篇文章【DataBinding使用教程(四):BaseObservable与双向绑定】中,我会以一个典型示例演示如何使用@InverseMethod注解以及它的强大之处。
总结:
所有的注解总算是讲完了,关于双向绑定的注解只是讲了基本的用法,具体更详细的用法及使用场景会在下一篇文章中讲解。
现在终于发现,想写一篇好文章真的太难了,要考虑好多好多东西,生怕自己哪里讲错了误人子弟,就算即便这样,可能这篇文章也还算不上好文章,并且个人能力有限,文章中难免会有讲的错误或者不恰当的地方,还请广大读者不吝指出。