上图展示了DataBinding做的事,但解决了 Android UI 编程的一个痛点(findViewById),可以说官方原生支持 MVVM 。
Data Binding 框架如果能够推广开来,也许 RoboGuice、ButterKnife 这样的依赖注入框架会慢慢失去市场,因为在 Java 代码中直接使用 View
变量的情况会越来越少。
准备
新建一个 Project,确保 Android 的 Gradle 插件版本不低于 1.5.0-alpha1:
|
|
然后修改对应模块(Module)的 build.gradle:
|
|
注意:Android stuido 的版本一定要大于1.3,而且Android Studio目前对binding对象没有自动代码提示,只会在编译时进行检查。
就是这么简单,但是1.3及以前的版本,对于环境的搭建,可能就会麻烦一点(没事1.3的环境搭建方法,网上多得是,后面参考资料链接里也有)。
基础
工程创建完成后,我们通过一个最简单的例子来说明 Data Binding 的基本用法。
布局文件
使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup
,而是变成了 layout
,并且新增了一个节点 data
。
|
|
要实现 MVVM 的 ViewModel
就需要把数据(Model)与 UI(View) 进行绑定,data
节点的作用就像一个桥梁,搭建了 View 和 Model 之间的通路。
我们先在 xml 布局文件的 data
节点中声明一个 variable
,这个变量会为 UI 元素提供数据(例如 TextView
的 android:text
),然后在 Java 代码中把『后台』数据与这个 variable
进行绑定。
下面我们使用 Data Binding 创建一个展示用户信息的表格。
数据对象
添加一个 POJO 类 - User
,非常简单,两个属性以及他们的 getter 和 setter。
|
|
稍后,我们会新建一个 User
类型的变量,然后把它跟布局文件中声明的变量进行绑定。
定义 Variable
回到布局文件,在 data
节点中声明一个 User
类型的变量 user
。
|
|
其中 type
属性就是我们在 Java 文件中定义的 User
类。
当然,data
节点也支持 import
,所以上面的代码可以换一种形式来写。
|
|
这里需要注意的是:import 并不能像java 一样可以 import xx.xxx.*,必须具体到写清楚每个要导入的类名。
然后 com.android.databinding
会根据 xml 文件的名称 Generate 一个继承自 ViewDataBinding
的类。 当然,IDE 中看不到这个文件,需要手动去 build 目录下找。
例如,这里 xml 的文件名叫 activity_basic.xml
,那么生成的类就是 ActivityBasicBinding
>
+ Binding类里将会包含通过variable设置name的getter和setter方法。如上面的setUser,getUser等(后面还会讲到)。
+ 如果控件设置了id,那么该控件也可以在binding类中找到,这样就不需要findViewById来获取View了。
注意
java.lang.*
包中的类会被自动导入,可以直接使用,例如要定义一个 String
类型的变量:
|
|
或者使用{String.valueOf()
静态方法(同后面讲到的静态类的使用)
|
|
绑定 Variable
修改 BasicActivity
的 onCreate
方法,用 DatabindingUtil.setContentView()
来替换掉 setContentView()
,然后创建一个 user
对象,通过 binding.setUser(user)
与 variable
进行绑定。
|
|
除了使用框架自动生成的 ActivityBasicBinding
,我们也可以通过如下方式自定义Binding类的名称和包名:
|
|
注意
ActivityBasicBinding
类是自动生成的,所有的 set
方法也是根据 variable
名称生成的。例如,我们定义了两个变量。
|
|
那么就会生成对应的两个 set 方法。
|
|
使用 Variable
数据与 Variable 绑定之后,xml 的 UI 元素就可以直接使用了。
通过@{}语法来设置,这个语法相当与一个标记,告诉编译器,这里需要用到DataBinding框架生成相应代码。包括后面会讲到的DataBinding中的自定义属性。
|
|
自动避免 NullPointerException:生成的数据绑定代码自动检查 Null 并且避免 NullPointerException。 例如,对于表达式 @{user.lastName} ,如果 user 是 null, 则表达式的值为默认值 null, 如果引用的为 user.age, age 为 int 类型,则默认值为0.
至此,一个简单的数据绑定就完成了,可参考完整代码
高级用法
使用类方法
首先定义一个静态方法
|
|
然后在 xml 的 data
节点中导入:
|
|
使用方法与 Java 语法一样:
|
|
类型别名
如果我们在 data
节点了导入了两个同名的类怎么办?
|
|
这样一来出现了两个 User
类,那 user
变量要用哪一个呢?不用担心,import
还有一个 alias
属性。
|
|
集合和数组
常见的集合可以使用 [] 操作符来引用里面的元素,例如 arrays, lists, sparse lists, 和 maps。集合泛型左尖括号需要使用转译<
|
|
重要提示:使用后xml文件中,目前仍然有红色错误提示,应该是IDE的兼容问题,但不影响编译和使用
如果未使用转义而直接使用<
号,会出现如下错误:
字符串字面量(String Literals)
如果属性的值使用单引号(’),则可以在表达式中使用双引号(”)来引用字符串字面量:
|
|
如果属性值使用双引号定义,则需要使用 " 或者反引号 (`)。
|
|
EL表达式(Expression Language)
DataBinding支持的表达式有:
数学表达式: + - / * %
字符串拼接 +
逻辑表达式 && ||
位操作符 & | ^
一元操作符 + - ! ~
位移操作符 >> >>> <<
比较操作符 == > < >= <=
instanceof()
分组操作符 Grouping ()
字面量 - character, String, numeric, null
强转、方法调用
函数调用
字段访问
数组访问 []
三元操作符 ?:
示例:
|
|
不支持的语法
- this
- super
- new
- Explicit generic invocation 显式泛型调用???
Null Coalescing 运算符
|
|
就等价于
|
|
属性值
通过 @{}
可以直接把 Java 中定义的属性值赋值给 xml 属性。
|
|
使用资源数据
这个例子,官方教程有错误,可以参考Android Data Binder 的一个bug,完整代码在此
|
|
在String资源中如果使用到格式化字符(需带参数的字符串),如
|
|
则可以这样调用
|
|
多个参数时:
|
|
复数字符串(关于plurals):
和String一样:
|
|
如果 复数字符串使用了多个变量,则需要提供所有的变量:
|
|
有些资源的引用方式有变化:
资源类型 | 布局 View 中引用方式 | 表达式引用方式 |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
Event Binding (事件绑定)
事件处理器:
|
|
布局中使用:
|
|
Activity的java中实现该接口UserFollowEvent:
|
|
这里是采用了接口形式,非接口形式不过是把接口变成了实现类:
|
|
Observable Binding
本来这一节的标题应该叫双向绑定,但是很遗憾,现在的 Data Binding 暂时支持单向绑定,还没有达到 Angular.js 的威力。
要实现 Observable Binding,首先得有一个 implement
了接口 android.databinding.Observable
的类,为了方便,Android 原生提供了已经封装好的一个类 - BaseObservable
,并且实现了监听器的注册机制。
我们可以直接继承 BaseObservable
。
|
|
BR
是编译阶段生成的一个类,功能与 R.java
类似,用 @Bindable
标记过 getter
方法会在 BR
(BR类自动生成的)中生成一个 entry(字段标识(int))。
通过代码可以看出,当数据发生变化时还是需要手动发出通知。 通过(如set方法里)调用 notifyPropertyChanged(BR.firstName)
可以通知系统 BR.firstName
这个 entry
的数据已经发生变化,需要更新 UI。
除此之外,还有一种更细粒度的绑定方式,可以具体到成员变量,这种方式无需继承 BaseObservable
,一个简单的 POJO 就可以实现。
|
|
布局中,直接使用变量:
|
|
代码中设置/改变数据,ObservableField都会有一对get和set方法,所以使用起来也很方便了:
|
|
系统为我们提供了所有的 primitive type 所对应的 Observable类,例如 ObservableInt
、ObservableFloat
、ObservableBoolean
等等,还有一个 ObservableField
对应着 reference type。
剩下的数据绑定与前面介绍的方式一样,具体可参考ObservableActivity。
更高级的还有Observable Collections:
ObservableArrayMap,ObservableArrayList。使用和普通的Map、List基本相同,直接看代码:
|
|
在布局中,使用方式和普通的集合一样,如果看不太懂,可以往上翻博客,看上面的集合是怎么使用的。再来看java文件,怎么设置数据,
|
|
和List、Map使用方法一模一样!!!
带 ID 的 View
Data Binding 有效降低了代码的冗余性,甚至完全没有必要再去获取一个 View 实例,但是情况不是绝对的,万一我们真的就需要了呢?不用担心,只要给 View 定义一个 ID,Data Binding 就会为我们生成一个对应的 final
变量。
|
|
上面代码中定义了一个 ID 为 firstName* 的 TextView
,那么它在ViewDataBinding里对应的变量就是
|
|
只要我们给了view一个id,那么框架就会在ViewDataBinding中自动帮我们保存这个view的实例,变量名就是我们设置的id。
具体代码可参考 ViewWithIDsActivity.java
ViewStubs
关于ViewStubs可查看Android实战技巧:ViewStub的应用
xml 中的 ViewStub
经过 binding 之后会转换成 ViewStubProxy
, 具体代码可参考 ViewStubActivity.java
简单用代码说明一下,xml 文件与之前的代码一样,根节点改为 layout
,在 LinearLayout
中添加一个 ViewStub
,添加 ID。
|
|
在 Java 代码中获取 binding
实例,为 ViewStubProy
注册 ViewStub.OnInflateListener
事件:
|
|
获得Binding和布局的方式
前面讲到在Activity中通过DataBindingUtil.setContentView替换setContentView方法设置布局,同时获取Binding对象,现在有个问题了,如果我们是在Fragment中使用呢?Fragment没有setContentView怎么办?不要着急,Data Binding也提供了inflate的支持!
方式一
|
|
方式二(特殊情况可能需要用到,不然很没必要)
|
|
Dynamic Variables
完整代码可以参考 dynamic
以 RecyclerView
为例,Adapter
的 DataBinding 需要动态生成,因此我们可以在 onCreateViewHolder
的时候创建这个 DataBinding,然后在 onBindViewHolder
中获取这个 DataBinding。
|
|
BR.user的user常量是怎么产生的?布局里的中的user属性值。有点类似以前的R里面的id。 有人会问了如果别的实体(model)也有相同的user属性怎么办?那他到底使用哪个呢?其实这是不会冲突,因为在不用的地方用,他的上下文(Binging)不一样,所以不会冲突。也是和以前的R里面的常量是一回事情。只是把它放到BR里面去了。所以我猜想BR的全称应该是(Binding R(R就是以前我们用的常量类))虽然官方没有说明。
注意此处 DataBindingUtil
的用法:
|
|
还有另外一种比较简洁的方式,直接在构造 Holder 时把 View
与自动生成的 XXXBinding
进行绑定。
|
|
Attribute setters-自定义属性
有了 Data Binding,即使属性没有在 declare-styleable
中定义,我们也可以通过 xml 进行赋值操作。
为了演示这个功能,我自定义了一个 View - NameCard,属性资源 R.styleable.NameCard 中只定义了一个 age
属性,其中 firstName
和 lastName
只有对应的两个 setter
方法。(这里指的是这个自定义View中有setter
方法)
只要有 setter
方法就可以像下面代码一样赋值:
|
|
onClickListener
也是同样道理,只不过我们是在 Activity
中定义了一个 Listener
。
Custom Setter(自定义Setter方法)
上面说的是自定义属性,而有些时候我们需要自定义binding逻辑,如:在一个TextView上设置大小不一样的文字,这个时候就需要我们自定义binding逻辑了.
在比如我们为ImageView加载图片,通过总是通过类似这样的的代码来实现:
|
|
如果我们自定Setter方法,那么这些都可以是自动的。怎么实现呢?
|
|
@BindingAdapter({“imageUrl”}) 这句话意味着我们自顶一个imageUrl属性,可以在布局文件中使用。当在布局文件中设置该属性的值发生改变,会自动调用loadImage(ImageView view, String url)方法。 我们定义了一个Utils类,这个类你可以随便起名,该类中只有一个静态的方法loadImage,方法名也是随意,该方法有两个参数,一个是需要设置数据的view,一个是我们需要的url。
布局中使用:
|
|
@BindingAdapter value值的规则
- android自带属性:
|
|
- 其他自定义view自带属性:
|
|
- 需要DataBing框架自动生成的属性直接就是属性name
|
|
提示
前面可以加上”[xmlns name]:”,
如”bind:imageUrl”,不和前两种规则已有属性冲突就不影响,也可以正常运行,只是Build项目后会提示,
Error:(26, 24) 警告: Application namespace for attribute bind:imageUrl will be ignored.
有人也许注意到了value是数组,是的,可以传入多个参数,代码来了:
|
|
再来看下如何实现:在一个TextView上设置大小不一样的文字(其实是一样的)
|
|
|
|
注意:使用自定义Setter,需要使用dataBinding语法。以下用法是不对的:
|
|
转换器 (Converters)
在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过 DataBinding 可以进行转换。
例如,下面代码中如果要为属性 android:background
赋值一个 int
型的 color 变量:
|
|
只需要定义一个标记了 @BindingConversion
的静态方法即可:
|
|
和上面@BindingAdapter
一样,我们不需要关心这个convertColorToDrawable在哪个类中,重要的是他的@BindingConversion注解,这个方法接受一个int类型的变量,正好我们的Android:background设置的就是一个int类型的值,在方法内部我们将这个int类型的变量转换成ColorDrawable类型的Drawable并且返回。这样UI上就显示出我们转化好的背景。
还有个例子,比如android:text接收的是String和int Id,我们在xml中设置为@{data},data为Data对象变量,然后就可以通过@BindingConversion的方法接收一个Data对象把他format成指日期格式的String返回!
非常重要
使用 Converter 一定要保证它不会影响到其他的属性,例如这个
@BindingConversion
- convertColorToString 就会影响到android:visibility, 因为他们都是都符合从 int 到 int 的转换。
具体代码可参考 ConversionsActivity.java。
include
|
|
name.xml 和 contact.xml都必须包含
用法还可以参考代码 IncludeActivity.java
如果在非根节点的 ViewGroup 中使用 include
会导致 crash,已经在 StackOverflow 上提了一个问题Android Data Binding makes app crash when using include tag in a non-root ViewGroup,直されたそうですけど。
参考文档
官方教程 - Data Binding Guide
本文基于此文修改 - 精通 Android Data Binding
Android Databinding: Goodbye Presenter, hello ViewModel!——介绍MVP和MVVM,以及使用DataBinding后的代码优势
Android-MVVM架构-Data Binding的使用
来自官方的Android数据绑定(Data Binding)框架①
来自官方的Android数据绑定(Data Binding)框架②
Android数据绑定框架DataBinding,堪称解决界面逻辑的黑科技-入门推荐
Android官方数据绑定框架DataBinding(一)
Android官方数据绑定框架DataBinding(二)
参考Demo
精通 Android Data Binding - LyndonChin/MasteringAndroidDataBinding
Android-MVVM架构-Data Binding的使用 - chiclaim/awesome-android-mvvm
googlesamples/android-architecture todo-databinding
分支