2015年谷歌 IO大会上提出了 data binding框架,通过data binding的技术,我们可以在Android上实现MVVM模式,通过数据绑定的方式能实现视图的自动更新。
1.关于设计模式:
我们常见的设计模式有MVC,MVP,MVVM。
MVC:Model数据层 View视图层 Controller控制层
用户通过视图层将指令发送到控制层,控制层处理逻辑后更新数据层,数据层把更新的数据显示到视图层,完成一个完整流程,这个过程中,视图层和数据层是有互相依赖关系的。
MVP:Model数据层 View视图层 Presenter
MVP模式通过Presenter进行控制,它隔断了视图层和数据层的联系,视图层和控制层通过接口的方式进行交互。
MVVM:Model数据层 View视图层 ViewModel
MVVM模式可以认为是MVP模式是延生,不同之处是视图层和控制层之间的交互方式由接口变为了数据绑定,通过数据的双向绑定技术,实现视图的自动更新。
2.data binding特点
(1)通过使用data binding,可以把一些业务逻辑抽离,将一部分逻辑放到ViewModel中处理,减少Activity、Fragment中的代码。
(2)代码结构更加清晰,视图层和数据层实现了解耦,更有利于单元测试。
(3)数据绑定的工作在xml文件中进行,所以可能不利于调试。
3.使用环境
(1)开发环境:目前data binding只在Android studio 1.3版本以上使用
(2)编译环境:
在项目根目录的gradle文件中添加dependencies
dependencies {
classpath "com.android.tools.build:gradle:1.2.3"
classpath “com.android.databinding:dataBinder:1.0-rc0"
}
在所有项目的gradle文件中添加data binding支持
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
(1)View绑定的表达式
<p class="p1"><span class="s1"><</span><span class="s2">layout</span><span class="s1"> xmlns:android="http://schemas.android.com/apk/res/android"></span></p><p class="p2"><span class="s3"> </span><span class="s1"><data></span></p><p class="p2"><span class="s1"> <variable name="user" type="com.example.User"/></span></p> <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="<span style="color:#ff0000;">@{user.firstName}</span>"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="<span style="color:#ff0000;">@{user.lastName}</span>"/>
</LinearLayout>
</layout>
在layout文件中,起始跟标签是layout,它包含一个data元素和一个View根元素,data中的variable指定了需要绑定的类,name是该类对象的名称,type是该类的包名路径,在TextView通过@{ }表达式绑定类的属性。
(2)Data对象
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
Data对象,即普通的javabean。
(3)Binding数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
Android Studio会根据Layout的名称自动生成ViewModel类,比如activity_main.xml会自动生成一个ActivityMainBinding类,ActivityMainBinding中的方法,会根据layout中的属性自动生成。比如在layout中指定了user变量,ActivityMainBinding类中会自动生成get/setUser方法。
也可以通过以下方法来获取binding对象:
//使用Binding类型创建
ActivityMainBinding.inflate binding = ActivityMainBinding.inflate(layoutInflater);
//绑定根布局
ActivityMainBinding binding = ActivityMainBinding.bind(viewRoot);
在ListView的Adapter中获取binding的方式:
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
通过以上三步,就可以完成一个最简单的数据绑定。
(4)数据绑定的方法
a. import语法
<data>
<import type="android.view.View"/>
</data>
类似java语法,可以把所需类通过imoprt引入
b. 类名相同
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
<span style="color:#ff0000;">alias=“MyView"</span>/>
…
<TextView
android:text=“@{MyView.width}"
…
当类名相同时,可以使用alias属性来区分。
c. import集合类型
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
<TextView
android:text="@{userList[0].userName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
d. 使用类的静态方法
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在类中定义的静态方法也可以直接绑定使用。
e. 自定义binding类名
默认情况下,自动生成的Binding类会放置在databinding包下,如果模块包是com.example.app,那么它会放置在com.example.app.databinding。可以通过data元素的class属性来重命名或者放置在不同的包中。
<data class="ContactItem">
...
</data>
<data class="com.example.ContactItem">
...
</data>
f. Includes
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<span style="color:#ff0000;">xmlns:bind="http://schemas.android.com/apk/res-auto"</span>>
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
…>
<span style="color:#ff0000;"><include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/></span>
</LinearLayout>
…
在layout文件中有其他includes的xml文件要使用绑定数据时,需要添加一个命名空间,然后使用bind属性绑定,前提是在name.xml和contact.xml文件中也要添加。
(5)绑定表达式的语法
a. @{ }表达式支持的运算
数学 + - / * % 字符串连接 + 逻辑 && ||
二进制 & | ^ 一元运算 + - ! ~ 移位 >> >>> <<
比较 == > < >= <= instanceof 分组 ()
null Cast 方法调用
数据访问 [] 三元运算 ? :
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
b. Null的合并操作
android:text="@{user.displayName ?? user.lastName}"
?? 表示如果左边不为null,使用左边,如果左边为null,使用右边。
c. 避免空指针
在表达式@{user.name}中,如果user是null,user.name会赋予它的默认值(null)。如果你引用user.age,age是int类型,那么它的默认值是0。
d. 集合
android:text="@{list[index]}"
android:text="@{map[key]}"
e. 访问Resources资源
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
f. 表达式
android:text=‘@{map["firstName"]}'
android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
以上三种表达式都可以使用
(6)Data对象
(1)-(3)步骤完成了一个最简单是数据绑定,但是这样数据源仅仅只能做到绑定,并不能实现动态更新,要实现数据的自动更新,就必须要用Observable的方式存储数据,Observable是一个接口,接口内提供了一个监听,并且有BaseObservable类供我们实现这个监听,data binding给我们提供了三种不同的数据变化通知机制,把这些对象绑定到view,当observable对象更新后,UI会自动更新。这三种机制是:Observable,ObservableFields,Observable Collections。
a. Observable
private static class User extends <span style="color:#ff0000;">BaseObservable</span> {
private String firstName;
<span style="color:#ff0000;">@Bindable</span>
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
<span style="color:#ff0000;">notifyPropertyChanged(BR.firstName);</span>
}
}
Data数据需要继承BaseObservable类,并且在get方法上添加@Bindable注解,在set方法中添加notifyPropertyChanged(BR.firstName),BR类是通过注解生成的,通过BR类对需要更新的数据进行设置。
b. ObservableFields
private static class User extends BaseObservable {
<span style="white-space:pre"> </span>public final ObservableField<String> firstName = new ObservableField<>();
<span style="white-space:pre"> </span>public final ObservableInt age = new ObservableInt();
}
ObservableFields为Data对象提供了一种更为简化的写法,可以通过ObservableFiled的泛型指定数据类型,也可以直接使用data binding提供的ObservableInt、ObservableString、ObservableFloat等常用几个数据类型的封装类。访问data对象的时候,直接使用set/get方法:
<span style="white-space:pre"> </span>user.firstName.set("Google");
<span style="white-space:pre"> </span>int age = user.age.get();
c. O bservable Collections
data binding 也向我们提供了常用集合的封装:ObservableArrayList,ObservableArrayMap,使用方法和Map集合、List集合方式一样。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中进行绑定:
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
(7)设置View的id
虽然data binding帮我们避免了findViewById等繁琐的获取View的过程,但是有时候我们也会碰到需要制定View的id的情况,这时候通过data binding也可以直接获取到View
<data>
<variable name="user" type=“com.example.User"/>
</data>
…
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.userName}"
android:<span style="color:#ff0000;">id="@+id/userName"</span>/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.userPassword}"
android:<span style="color:#ff0000;">id="@+id/userPassword"</span>/>
指定View的id后,Binding类会自动生成相应的控件对象,我们可以通过binding对象直接获取控件:
TextView mTvUserName = binding.userName;
TextView mTvPassword = binding.userPassword;
有错误之处请批评指正,感激不尽。
参考资料:
http://segmentfault.com/a/1190000003056818
http://www.cnblogs.com/dxy1982/p/3793895.html
http://www.zhihu.com/question/30976423