Android Data Binding介绍
Data Binding是一个MVVM的架构框架,使用Data Binding对于我们开发应用有下面好处:
- 可以直接在layout布局中的xml中绑定数据
- 分离视图与业务逻辑
- 适用于android 2.1以上的版本
搭建Data Binding环境
使用Data Binding需要下面条件:
- gradle1.3 以上
- android studio1.3以上(当然现在都到了2.2了^_^)
在项目的根目录下build.gradle添加dependenncy
为用到 Data Binding 的模块添加插件,修改对应的 build.gradle
需要注意的是,如果studio在2.0版本之后,可以直接在android中添加
dataBinding{
enabled = true
}
Data Binding入门
下面我通过一个简单的栗子,来实现业务和逻辑分离,并在不居中直接绑定数据。
新建一个java bean类
public class UserInfo {
public String name;
public String phone;
}
添加layout布局
在layout中绑定数据,我们需要在原本的layout的跟布局中使用layout,并制定其和那个类相关。
另外我们也可以通过import来引入我们的user类对象
注意:java.lang.* 包中的类会被自动导入,可以直接使用
使用DataBindingUtil绑定布局
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// 通过DataBindingUtil重新设置布局
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
// 创建一个UserInfo,并绑定数据
UserInfo userInfo = new UserInfo();
userInfo.name = "haha";
userInfo.phone = "1234567890";
// setUser方法是根据在xml中通过variable配置的user自动生成的
binding.setUser(userInfo);
}
可以看到,系统自动为我们的activity_main布局生成了对应的ActivityMainBinding类,我们也可以自己定义该Binding类的类名
<data class="TestLayoutMain">
<import type="com.example.myapplication.UserInfo" alias="AliasUserInfo"/>
<variable
name="user"
type="AliasUserInfo"/>
</data>
此时生成的Binding类名获取方式如下:
TestLayoutMain binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);
布局中引用user类
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
/>
另外,我们也可以使用jdk中提供的数据类型来进行设置,比如:
<variable
name="sex"
type="String">
</variable>
为UserInfo对象添加sex属性
public String sex;
在布局中使用该属性
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{sex}"
/>
通过引用布局绑定数据
另外我们可以通过include标签来引用我们的指定的布局,并且传入user对象。
创建布局
user_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.myapplication.UserInfo"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{user.phone}"
/>
</LinearLayout>
</layout>
在主布局中引用该布局
接下来,我们需要在主布局中通过include标签,引用到该布局,并且传递我们的user对象。
<include layout="@layout/user_layout"
bind:user="@{user}"
/>
使用类方法
我们也可以在布局中直接使用定义好的类里面的方法
public class DataUtils {
public static String changeCharacter(String originalStr) {
if (null != originalStr) {
return originalStr.toUpperCase();
}
return null;
}
}
在布局中引入当前类
<data>
<import type="com.example.myapplication.UserInfo"/>
<import type="com.example.myapplication.DataUtils"/>
<variable
name="user"
type="UserInfo"/>
</data>
直接调用该类的静态方法:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{DataUtils.changeCharacter(user.name)}"
/>
类型别名
如果我们在布局中导入了两个不同包下的UserInfo对象,此时我们到底该使用哪一个呢。其实我们可以通过alias别名来进行设置。
三元运算符
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="@{user.name ?? user.phone}"
/>
上面@{user.name ?? user.phone}等价于:
user.name != null ? user.name : user.phone
android:visibility="@{user.mIshow ? View.VISIBLE : View.GONE}"
使用资源数据
这里,我们新创建两个颜色资源:
<color name="red">#ff0000</color>
<color name="blue">#0000ff</color>
在布局中使用:
<plurals name="numberOfSongsAvailable">
<item quantity="one">One song found.</item>
<item quantity="other">%d songs found.</item>
</plurals>
在布局中使用:
类型转换器
DataBinding也为我们提供了一个类型转换器,当xml中的类型,和需要显示的类型不同的时候,我们只需要在对应的实体类中添加一个@BindingConversion注解即可。
public class UserInfo {
public String name;
public String phone;
public boolean mIshow;
public String sex;
@BindingConversion
public static int convertToString(int color) {
switch (color) {
case Color.RED:
return R.string.red;
case Color.BLUE:
return R.string.blue;
}
return R.string.app_name;
}
}
可以看到,上面我们在text中返回的是int类型,所以这里通过BindingConversion注解进行类型转换。
<variable
name="time"
type="java.util.Date"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{time}"
/>
@BindingConversion
public static String convertDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
NullPointException的检测
在DataBinding中,会自动为我们避免出现空指针,比如:user.name和user.age,如果user是null,那么user.name会返回null,user.age是int类型,所以默认返回0
集合类型数据的引用
常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]来访问
- 在UserInfo中增加一个Map集合
public Map<String,Object> map = new ArrayMap<>(3);
//添加设置数据
userInfo.map.put("hello",111);
userInfo.map.put("world","世界");
- 在布局中使用
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{String.valueOf((Integer)user.map["hello"])}'
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{user.map["world"]}'
/>
带Id的View
当我们在布局中为某一个控件增加id时候,DataBinding会自动生成该根据该id自动生成对应的实例,看下面栗子:
<TextView
android:id="@+id/dataBindId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
TestLayoutMain binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);
binding.dataBindId.setText("hello world");
ViewStub支持
<ViewStub
android:id="@+id/my_viewstub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/user_layout"
/>
TestLayoutMain binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);
binding.myViewstub.inflate();
ViewStub的使用
<ViewStub
android:id="@+id/my_viewstub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/stub_layout"
/>
// 注意这里AS可能会标红,可能是支持不好,但是不影响编译和运行
binding.myViewstub.getViewStub().inflate();
事件绑定
同时我们可以通过DataBinding来绑定事件到指定的View,看下面的栗子:
<variable
name="clickListener"
type="android.view.View.OnClickListener" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:onClick="@{clickListener}"
/>
我们知道,当在xml布局文件中定义的每一个variable对象,系统都会生成其对应的setXXX方法。
TestLayoutMain binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.activity_main);
binding.setClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "setClickListener bind click ok", Toast.LENGTH_LONG).show();
}
});
监听数据变化
我们知道,很多时候我们的界面是需要实时改变数据的,当然DataBinding也为我们封装好了,看下面的栗子。
- 创建POJO实例
public class UserInfo extends BaseObservable{
public String name;
public UserInfo(String name) {
this.name = name;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
这里我们需要动态改变name属性
1. 实体类继承自BaseObservable类
2. 为需要动态改变的属性增加get和set方法
3. 在get方法添加@Bindable注解
4. 在set方法中调用notifyPropertyChanged(BR.xx);来通知数据变化,其中xx为需要改变的属性
- 在布局中引用该实体类
<import type="com.detail.pojo.UserInfo" alias="ObserUser"/>
<variable
name="obserUser"
type="ObserUser"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{obserUser.name}"
/>
- 在代码中动态改变属性内容
final com.detail.pojo.UserInfo user = new com.detail.pojo.UserInfo("hello");
binding.setClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
user.setName("world");
}
});
binding.setObserUser(user);
使用ObservableField监听数据变化
前面我们创建的实体类,需要为属性设置get和set方法并且需要在set方法中通过notifyPropertyChanged来通知数据变化。这样多少有些麻烦,好在系统为我们提供了一系列的ObservableField,如下:
ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,
ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable
通过这些封装ObservableField就无需提供get和set方法了。看下面栗子:
- 创建POJO实体类
public class UserInfo extends BaseObservable{
/**
* ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,
* ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable
*/
public ObservableField<String> name = new ObservableField<>();
public ObservableInt age = new ObservableInt();
public ObservableBoolean isShow = new ObservableBoolean();
}
- 创建布局,通过variable标签引用该POJO
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class="CustomeLayout">
<variable
name="user"
type="com.detail.pojo.UserInfo"/>
<variable
name="onClickListener"
type="android.view.View.OnClickListener"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{user.isShow ? "show" : "donotshow"}'
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:onClick="@{onClickListener}"
/>
</LinearLayout>
</layout>
- 在代码中动态改变属性
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CustomeLayout binding = DataBindingUtil.setContentView(MainActivity.this,R.layout.custom_main);
final com.detail.pojo.UserInfo user = new UserInfo();
user.age.set(22);
user.name.set("hello world");
user.isShow.set(true);
binding.setUser(user);
binding.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"age is :"+user.age.get()+" name is :"+user.name.get()+" isShow is :"+user.isShow.get());
user.age.set(33);
user.name.set("yes it changed");
user.isShow.set(false);
}
});
}
看到了吧,其实同样很简单,google工程师已经为我们封装的很完美了。
参考: Android官方数据绑定框架DataBinding(一)
Android官方数据绑定框架DataBinding(二)
Android官方数据绑定框架DataBinding(三)