背景
2015年的Google I/O上提出了Data Binding的库,意味着在Android开发中,也支持MVVM的开发模式。首先简单介绍一下MVVM,这种模式是由微软公司的提出的,并且在MVP上演变而来的一种架构模式。熟悉MVP的人应该知道View
和Model
通过Presenter
来完成交互,现在比较流行positive View
,也就是View
完全被动的展示数据,逻辑完全交给Presenter
。而在MVVM中,ViewModel
就是一个强化版的Presenter
,因为Data Binding Library能够帮ViewModel
生成Binding
大量的代码。下图为MVP和MVVM示意图
本期对Data Binding的用法做个介绍。
兼容性
这是很多人比较关心的地方,毕竟版本兼容一直Android中的重要话题,Data Binding Library 兼具灵活性和广泛的兼容性,它是一个Support Library(类似于v4和v7包)可以向下兼容到Android2.1(API level 7+)。
构建环境
Data Binding 要求Android Studio版本在1.3以上(好处是IDEA支持语法高亮提示等,详情请见 Android Studio Support for Data Binding)gradle的版本要在1.5.0-alpha1以上,而且需要在Android SDK manager中下载Support repository,随后在工程的build.gradle
中加入dataBinding
元素如下
android {
....
dataBinding {
enabled = true;
}
}
如果你的app module
依赖于一个使用了Data Binding library,那么app module
的build.gradle
文件也需要加入此元素。
先从Layout文件入手
Data Binding 的layout
文件和以往我们的形式有所不同,<Primary Root Element>
需要变成<layout>
,在<layout>
中添加了<data>
节点来定义需要用到的变量和需要的引用,<Primary Root Element>
则和<data>
平行于<layout>
之内,可以理解为<data>
负责数据部分,<Primary Root Element>
负责View
的展示。以下是个简单地例子
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
</data>
<RelativeLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.phone}" />
</RelativeLayout>
</layout>
在<data>
中的<variable>
声明了一个可能在layout
中使用的变量,包括name
和type
。如果使用<import>
之后type
就不需要写明类的全路径了
<data>
<import type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
<variable name="user" type="UserViewModel"/>
</data>
<import>
可以引用你需要的任何类就和普通的Java import关键字一样,例如
<import type="android.view.View"/>
引用之后这个类的方法就可以直接使用了,例如
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isRegister ? View.VISIBLE : View.INVISIBLE}">
但是这里可能会有两个名字重复的类,例如
<data>
<import type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
<import type="com.winter.huang.databinding.data.UserViewModel"/>
<variable name="user" type="UserViewModel"/>
</data>
这样看起来让人很困惑,因为对应关系看起来一团糟,可以在<import>
中加入alias
,就可以轻松的区分同名类了
<data>
<import type="com.winter.huang.databinding.viewmodel.UserViewModel" alias="User"/>
<import type="com.winter.huang.databinding.data.UserViewModel" alias="DataUser"/>
<variable name="user" type="User"/>
</data>
之后就可以在layout
中需要属性值的地方加入由"@{}"
语法包裹的表达式了,给TextView android:text
的赋值方法
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.phone}"/>
支持的操作符
数学运算符 + - / * %
字符串拼接 +
逻辑运算 && ||
二进制运算 & | ^
一元运算符 + - ! ~
位运算符 >> >>> <<
比较运算符 == > < >= <=
instanceof
Grouping ()
文字 - character, String, numeric, null
类型转换 cast
方法调用 methods call
字段使用 field access
数组使用 [] Arrary access
三元运算符 ? :
缺失的操作符
this
super
new
显示类型调用
关于显示类型调用可以看这里
此外还支持null的合并操作符??
,不同于三元运算符,??
代表如果前者不为null则直接使用前者的值,否则使用后者,例如
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.phone ?? @string/empty}">
如果user.phone
不为null显示user.phone
的值,为空则将在TextView
中显示一个空字符串。
空指针安全
自动生成的 DataBinding 代码会检查null
,避免出现NullPointerException
。例如在表达式中@{user.phone}
如果user == null
那么会为user.phone
设置默认值null
而不会导致程序崩溃(基本类型将赋予默认值如int
为0,引用类型都会赋值null
)
集合
支持 arrays, lists, sparse lists, maps,为了方便在获取item时可以使用[]
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
需要注意一点,在xml中<
需要用转义字符<
来代替。
Data Object
接下来看一下如何定义一个 POJO(plain-old Java object) 类,这个类就是ViewModel
public class UserViewModel {
public final String phone;
public final boolean hasRegister;
public UserViewModel(boolean hasRegister, String phone) {
this.hasRegister = hasRegister;
this.phone = phone;
}
}
由于字段都是final的,那么这个类的数据在读取它的数值之后永远不会在程序运行时改变,当然你也可以用一个 JavaBean object
public class UserViewModel {
private final String phone;
private final boolean hasRegister;
public UserViewModel(boolean hasRegister, String phone) {
this.hasRegister = hasRegister;
this.phone = phone;
}
public String getPhone() {
return phone;
}
public boolean isHasRegister() {
return hasRegister;
}
}
以上两种定义方式对于 Data Binding 而言是没有区别的,在给TextView android:text
赋值@{user.phone}
时,前一种会使用phone
后一种会使用getPhone()
。
绑定数据
默认情况下,BindingClass
会以layout
文件名来驼峰命名,并且以“Binding”
结尾,例如layout
文件为main_activity.xml
那么自动生成的Binding
类的名字就是MainActivityBinding
,你也可以自定义这个BindingClass
的名字,只需要在 <data>
加上class
如
<data class="MainBinding">
这样你的Binding
类名字就变成了MainBinding
。由于在<data>
中的<variable>
声明了一个user
变量,Data Binding自动生成了setUser(viewModel)
方法,这样就可以把ViewModel
和xml
关联起来了。
那么接下来在MainActivity
中只需要几行代码就能实现Data Binding:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserViewModel viewModel = new UserViewModel(false, "18588419291");
binding.setUser(viewModel);
}
或者你也可以在使用以下方法获取view
:
MainBinding binding = MainBinding.inflate(getLayoutInflater());
作为列表展示数据可以使用以下方法:
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, **false**);
现在运行程序,就能看到User
的phone
属性已经神奇的展示在TextView
的位置了。
关于MainBinding.java
在xml
中配置好之后,Data Binding Library 会帮你生成一个对应的.class
文件,经过IDEA
的编译我们可以看到自动生成的代码,在AndroidStudio中切换到Project
视图app-build-intermediates-classes-debug-Your PackageName
下会有一个databinding
文件夹,这里面就有对应的MainBinding.class
我们可以大概看一下
public class MainBinding extends android.databinding.ViewDataBinding {
private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;sViewsWithIds = null;
}
// views
private final android.widget.RelativeLayout mboundView0;
public final android.widget.TextView textView;// variables
private com.winter.huang.databinding.viewmodel.UserViewModel mUser;
// values
// listeners
// Inverse Binding Event Handlers
public MainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
super(bindingComponent, root, 0);
final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes,sViewsWithIds);
this.mboundView0 = (android.widget.RelativeLayout) bindings[0];
this.mboundView0.setTag(null);
this.textView = (android.widget.TextView) bindings[1];
this.textView.setTag(null);
setRootTag(root);
// listeners
invalidateAll();}
...
}
可以看到每个xml
中的View
在自动生成的类中都会有对应的一个final
的变量与之对应,那么在MainActivity
中就不需要再使用findViewById
,可以直接访问该变量
mainBinding.textView.setText(user.phone);
同时在xml
中也可以直接使用,例如使用checkBox.checked
属性做判断
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkBox"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:visibility="@{checkBox.checked ? View.VISIBLE : View.GONE}"/>
这个类会随着xml
中一些修改而在编译时重新生成,这就是Data Binding帮我们做的一个非常重要的桥梁,这期就先到这里,下期继续介绍Data Binding的事件绑定。