怎么配置DataBinding
在Module的gradle 文件下,AndroidStudio版本不通开启的方式也不同
AndroidStudio 3.x 版本
android {
...
dataBinding{
enabled = true
}
}
AndroidStudio 4.x 版本(也可以使用3.0的方式开启,但是4.0推荐使用下面的方法)
android {
...
buildFeatures{
dataBinding = true
}
}
这里有一个坑,就是如果子Module配置了DataBinding,必须在主Module也配置
怎么让一个 View 可以绑定数据
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
比原来的layout文件多了layout
标签和data
标签,这样格式的xml布局文件就是一个支持DataBinding的布局文件,可以通过快捷键Alt
+Enter
键来快速生成
DataBinding 中的被观察者 BaseObservable
如果想要你的UI随着数据的更新而更新,还需要实现BaseObservable
BaseObservable 是一个被观察者,UI组件会观察BaseObservable,如果数据发生了变化,则UI会更新。这里可以把BaseObservable 理解为数据的包装类。
看一个简单的实现
public class UserEntity extends BaseObservable {
@Bindable
private String nickname;
@Bindable
private String headUrl;
public UserEntity(String nickname, String headUrl) {
this.nickname = nickname;
this.headUrl = headUrl;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
notifyPropertyChanged(BR.nickname);
}
public String getHeadUrl() {
return headUrl;
}
public void setHeadUrl(String headUrl) {
this.headUrl = headUrl;
notifyPropertyChanged(BR.headUrl);
}
}
首先数据类实现BaseObservable,然后对需要被观察的数据用@Bindable
注解
重写set/get 方法,然后在set 方法(数据更新的地方)调用notifyPropertyChanged(BR.id)
,UI就会被通知刷新UI
BR.id是DataBinding生成的id,用来区分数据的id,如果BR下面没有你的id,先检查一个有没有注解,然后再Rebuild 一下
单向绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="userInfo"
type="com.xiaoyu.mvvmdemo.UserEntity" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@{userInfo.nickname}" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/nickname_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text='@{"昵称:"+userInfo.nickname}' />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
单向绑定的话比较简单,通过@{}
的方式就可以把数据绑定到UI上了
这里写了两个绑定,一个是""
,一个是''
,如果有做字符串操作的(比如例子中的拼接字符串),是不能使用""
进行绑定的
双向绑定
双向绑定相对于单向绑定的语法上来说,只多了一个=
号
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:text="@={accountNumber}" />
双向绑定在使用的过程中要注意到一点,就是绑定的这个属性,是否支持双向绑定,如果不支持,编译肯定是不会通过的。
下面是谷歌实现的双向绑定
- AbsListView
- android:selectedItemPosition
- CalendarView
- android:date
- CompoundButton
- android:checked
- DatePicker
- android:year
- android:month
- android:day
- NumberPicker
- android:value
- RadioGroup
1.android:checkedButton - RatingBar
- android:rating
- SeekBar
- android:progress
- TabHost
- android:currentTab
- TextView
- android:text
- TimePicker
- android:hour
- android:minute
那么如果说系统提供的绑定方法不能够实现现有的需求怎么办?这里就需要到下面的自定义绑定方法了
自定义绑定 - 单向绑定
自定义单向绑定比较简单。只需要一个注解,一个方法就可以了
这里写一个用Glide加载网图,并且设置占位图的绑定方法
@BindingAdapter(value = {"bindUrl", "bindPlaceholder"}, requireAll = false)
public static void bindUrlAndPlaceholder(ImageView view, String url, int placeholder) {
Glide.with(view).load(url).apply(new RequestOptions().placeholder(placeholder)).into(view);
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.xiaoyu.mvvmdemo.R" />
<variable
name="userInfo"
type="com.xiaoyu.mvvmdemo.UserEntity" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
bindPlaceholder="@{R.drawable.ic_launcher_background}"
bindUrl="@{userInfo.headUrl}"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="50dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
@BindingAdapter
这个注解是用来说明,我这个方法是一个绑定方法注解里需要两个参数value
和requireAll
value
的类型是一个数组,数组的长度为方法参数长度-1(去掉View参数),然后顺序一一对应,bindUrl
和url
对应,bindPlaceholder
和placeholder
对应,view
即被绑定的View
requireAll
这个参数默认为true
,它的意思是,value
里面的数据是否全部都要绑定
自定义绑定 - 双向绑定
单向绑定可以只是set方法,双向绑定需要做的是,当被观察的View属性发生变化的时候,需要把自身的值给更新。用SwipeRefreshLayout
举一个栗子
@BindingAdapter("bindIsRefreshing")
public static void setRefreshing(SwipeRefreshLayout refreshLayout, boolean isRefreshing) {
if (refreshLayout.isRefreshing() != isRefreshing) {
refreshLayout.setRefreshing(isRefreshing);
}
}
@InverseBindingAdapter(attribute = "bindIsRefreshing", event = "onRefreshChange")
public static boolean isRefreshing(SwipeRefreshLayout refreshLayout) {
return refreshLayout.isRefreshing();
}
@BindingAdapter("onRefreshChange")
public static void setOnRefreshListener(SwipeRefreshLayout refreshLayout, InverseBindingListener inverseBindingListener) {
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
inverseBindingListener.onChange();
}
});
}
setRefreshing
这个方法和 单向绑定一样,不需要多解释。
isRefreshing
和setOnRefreshListener
这两个方法需要对照着看,
先看isRefreshing
这个方法,返回了SwipeRefreshLayout.isRefreshing()
,然后看上面的@InverseBindingAdapter
注解,有两个参数,attribute
表示被绑定的属性,我们上面因为是定义的bindIsRefreshing,所以这里和上面一样bindIsRefreshing
event
这个参数,可以理解为isRefreshing
的调用时机。这里转到底三个方法setOnRefreshListener
,这个方法中的InverseBindingListener
是一个DataBinding的接口,需要在属性发生变化时回调onChange
方法,这样双向绑定的观察者就会知道属性发生了变化,就会更新自身的属性值
当栗子上的SwipeRefreshLayout
触发OnRefreshListener.onRefresh
时,会回调到InverseBindingListener.onChange
方法,然后观察者收到回调后,就会调用isRefreshing
方法来更新属性
需要注意的是,双向绑定中的set方法一定要加属性值得判断,就是当属性相同时,不调用View的set方法,避免触发死循环