Android DataBinding自定义数据双向绑定 笔记
在学习dataBinding的数据双向绑定时,针对自定义特性的双向数据绑定看的一头雾水,现在有了大致了解,记录一下
1、XML中使用方式:
单向数据绑定
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
双向数据绑定,在单向上加上=
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>
相信大家对于如何在xml总定义双向数据绑定还是很清楚,但是对于如何在代码中使用及操作就有点懵了,下面说下流程。
2、代码中使用方式(分2种情况)
情况一、数据绑定是使用在View的系统属性上,且view中有提供setter方法
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"//android:checked属性是CheckBox类中有的属性,且类中有提供setter方法
/>
情况二、view有相关属性,但是类中没有提供setter方法,或数据绑定是使用在View的自定义属性上
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:paddingLeft="@={viewmodel.paddingLeft}"//android:paddingLeft属性是CheckBox类中有的属性,但是没有提供setter方法
/>
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.myText}"// app:time属性在EditText中不存在
/>
2.1 情况一(数据是绑定在系统属性上的)
针对这种使用的是view存在的属性且view中有提供setter方法,只需要在数据类ViewModel
类上做操作就可以了,很简单
public class ViewModel extends BaseObservable {
private boolean rememberMe;
private String text;
@Bindable
public Boolean getRememberMe() {
return rememberMe;
}
public void setRememberMe(Boolean value) {
// 避免死循环.
if (rememberMe != value) {
rememberMe = value;
// 通知界面更新.
notifyPropertyChanged(BR.remember_me);
}
}
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
Log.d(TAG, "情况1:源数据更改,代码中调用这个setter方法\n情况2:用户改变界面的值,触发反向设置值步骤3,在onChange中调用@Bindable修饰过的属性的setter方法,设置最新值");
if (TextUtils.equals(text, this.text)) {
return;
}
this.text = text;
notifyPropertyChanged(BR.text);
}
}
-
首先让数据类继承
BaseObservable
,这样数据类就具有了在数据更新时,可以通知界面的方法notifyPropertyChanged(BR.xx)
; -
针对要操作的绑定属性
rememberMe
,提供setter和getter方法; -
然后在属性的getter方法上添加注解
@Bindable
,系统会在BR类中生成一个条目BR.remember_me
,下次如果rememberMe
的值更新时,调用notifyPropertyChanged(BR.remember_me)
就可以使界面更新; -
当界面用户操作导致
CheckBox
的check
变动时,系统会调用提供的setter方法。
2.2 情况二(数据是绑定在自定义属性上的)
数据类ViewModel
类的设置,和情况一的一样设置,记得参考上面情况一设置数据类。
情况二,还需要针对xml中自定义的属性,添加解释类和方法。
2.2.1 先说几个个人理解,方便后续说明
(1)如果我们在xml中使用自定义属性,在当前view中不存在setter方法,例如:
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
很明显app:myText=
这个属性,EditText中是不存在setMyText()
方法的,出现这种情况的时候,编译器编译时就会报错,解决方法就是,告诉数据绑定模块,如果没有setter方法时,应该使用我们自己提供的方法。
(2)@BindingAdapter("属性名")
,
当给方法添加这个注解后,表示正向绑定时,设置的值应该通过这个方法操作,
// 负责解析xml中app:myText的属性,并调用下面这个方法
@BindingAdapter("app:myText")
public static void setText(EditText view, String text) {
if (TextUtils.equals(text,"1")) {
view.setBackground(new ColorDrawable(0xffff0000));
}else {
view.setBackground(new ColorDrawable(0xff0000ff));
}
}
(3)@InverseBindingAdapter("属性名")
当给方法添加这个注解后,表示反向绑定时,会调用这个方法,
@InverseBindingAdapter(attribute = "app:myText")
public static String getText(EditText view) {
return view.getText().toString();
}
(4)当我们在xml中使用
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
时
app:myText="@={viewmodel.text}"
系统(编译器,后续统称为系统吧)其实为我们分解成了下面这两部分
app:myText="@{viewmodel.text}"
app:myTextAttrChanged="@{inverseBindingListener}"//inverseBindingListener是系统生成的,我们现在不用管它是哪儿来的
看到上面这2个属性,大家应该就明白了,类似我们开头数据单向绑定,
app:myText="@{viewmodel.text}"
是用来做数据正向绑定的,app:myTextAttrChanged="@{inverseBindingListener}"
是用来做数据反向绑定的,一个回调对象
2.2.2 步骤及解释
通过2.2.1第4点的说明,我们看到系统帮我们生成的app:myText
和app:myTextAttrChanged
在EditText中是不存在的属性,肯定也没有setter方法,所以根据2.2.1第1点,此时就需要我们自定义方法,来让系统使用我们提供的方法
a.先解决数据正向绑定的问题,用2.2.1第2点的注解便可:
// 负责解析xml中app:myText的属性,并调用下面这个方法
@BindingAdapter("app:myText")
public static void setText(EditText view, String text) {
if (TextUtils.equals(text,"1")) {
view.setBackground(new ColorDrawable(0xffff0000));
}else {
view.setBackground(new ColorDrawable(0xff0000ff));
}
}
// 负责解析xml中app:myTextAttrChanged的属性,并调用下面这个方法
@BindingAdapter("app:myTextAttrChanged")
public static void setListener(EditText view, final InverseBindingListener listener) {
//内容需要在反向绑定数据时,再写。
}
通过上面这两个方法,算是完成了数据的正向绑定,
app:myText
的方法,其实就是判断给定的text是否等于1,然后给EditText设置不同的背景色;
app:myTextAttrChanged
的方法,就是告诉系统什么时候,触发回调;
b.数据反向绑定:
反向绑定其实,就是把用户在界面的改变,设置回我们自己的数据类上,本例中,就是把用户输入的文本,设置回viewmodel
的text
属性上
由于我们已经给数据类ViewModel中的text
属性添加了@Bindable
注解,并且上面提到的系统自动生成的InverseBindingListener
中的onChange()
方法是会帮我们调用text
属性的setter方法,所以我们只需要告诉数据绑定系统在什么时候设置和需要设置什么值便可。
① 在什么时候设置,由于是当用户输入时,我们就要同步获取输入内容,所以我们肯定是要在EditText上添加一个文本变化监听器,用户只要一输入,我们通过监听器就能马上知道,所以我们可以在我们自定义的app:myTextAttrChanged
方法中这样写:
// 负责解析xml中app:myTextAttrChanged的属性,并调用下面这个方法
@BindingAdapter("app:myTextAttrChanged")
public static void setListener(EditText view, final InverseBindingListener listener) {
if (listener != null) {
view.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Log.d(TAG, "用户改变界面的值,触发反向设置值步骤1,准备调用自动生成的onChange");
listener.onChange();
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
}
当EditText内容有变化时,就会调用系统生成的InverseBindingListener
的onChange()
方法,然后系统在onChange()
方法中就会自动把值设置给数据类对象ViewModel,至于值从哪儿来,我们看下面的②;
② 需要设置什么值,其实在InverseBindingListener
的onChange()
方法中,系统还会调用@InverseBindingAdapter("属性名")
修饰的方法,即2.2.1第3点的方法,所以我们只需要把我们要设置的值,通过这个方法返回便可:
@InverseBindingAdapter(attribute = "app:myText")
public static String getText(EditText view) {
return view.getText().toString();
}
至此便完成了,基本的数据双向绑定。
3、流程梳理
前提:
数据对象:
public class MyViewModel extends BaseObservable {
private static final String TAG = "MyObservable";
private String text;
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
if (TextUtils.equals(text, this.text)) {
return;
}
this.text = text;
notifyPropertyChanged(BR.text);
}
}
xml文件:
<EditText
android:id="@+id/time"
app:myText="@={viewmodel.text}"
/>
数据正向绑定流程:MyObservable.text
属性变化,调用属性的setter方法,触发notifyPropertyChanged(BR.text)
,根据xml中使用,会调用@BindingAdapter("app:myText")
注解的方法;
数据反向绑定流程:EditText内容变化时,触发InverseBindingListener
中的onChange()
方法,onChange()
中会调用获取值的@InverseBindingAdapter(attribute = "app:myText")
注解的方法,然后onChange()
中会调用MyObservable.text
属性的setter方法,触发notifyPropertyChanged(BR.text)
,根据xml中使用,会调用@BindingAdapter("app:myText")
注解的方法;
4、后话
其实数据绑定系统,也有自己实现的类,大家可以参考下:androidx.databinding.adapters.ViewBindingAdapter
。
当然还有@BindingMethods
和@InverseBindingMethods
,以及更高深的用法,我现在还在学习中,后续有时间会继续更新
由于掺杂很多个人的理解的,文中描述不一定完全正确,欢迎留言指正和讨论。