什么是双向绑定?其实在之前有一篇关于MVVM的文章中已经介绍过,比如登录的时候Edittext,当我们输入登录账号和密码时候,不需要通过Edittext.getText()获取内容,而是自动更新到M层,相反,当更新M层相关内容,也会自动更新到EditText尽心展示。所以,双向绑定就是:V层->M层、M层->V层。
在官方的介绍中,有两种方式能够实现自定义双向绑定:
- InverseBindingMethods+InverseBindingMethod+BindingAdapter
- InverseBindingAdapter+BindingAdapter
本篇文章讲解第一种方式,后续会讲解第二种方式。
一、InverseBindingMethods
该注解作用于类,官方的介绍是该注解,用于枚举属性、getter和事件相关联。并且value是InverseBindingMethod数组。
二、InverseBindingMethod
该注解作用于方法之上。
- type:需要自定义属性的类。必填项,比如RatingBar.class
- attribute:String类型。必填项,如android:rating
- event:用于通知view层属性值变化。可选项,否则使用默认配置:如ratingAttrChanged
- method:get方法,用于接收view层属性变化,通知Model层。可选项,否则使用默认配置:如getRating
三、BindingAdapter
该注解作用于方法之上。方法的第一个参数的类型必须为View类型,不然报错
- value:自定义任意属性,比如android:rating
- requireAll:boolean类型,可选项,返回true,所有的属性都需要支持,返回false,不需要支持所有属性。
四、自定义的属性是如何被调用的
比如在xml设置如下伪代码:
...
<data>
<variable
name="rt"
type="Float" />
</data>
<RatingBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numStars="10"
android:rt="@={rt}"
/>
...
编译时,编译器会扫描RatingBar使用databinding表达式的属性。这里会找到 android:rt。然后判断这个属性是不是RatingBar自有属性,如果是的话,会找对应的set方法(这里指setRt()方法)生成代码;如果不是自有属性,先找有没有对应且合适的set方法,有的话,就用set方法生成代码,没有的话,就会从所有定义的BindingAdapter中去找合适的方法。比如setRt()方法。
五、自定义双向绑定
@InverseBindingMethods(value = {
@InverseBindingMethod(
type = RatingBar.class,
attribute = "android:rating"
)
})
public class MyInverseBindingMethods {
@BindingAdapter("android:rating")
public static void setRating(RatingBar view, float value) {
if (view == null) {
return;
}
float rating = view.getRating();
if (rating == value) {
return;
}
//model->view
view.setRating(value);
}
@BindingAdapter(value = {"android:onRatingChanged", "android:ratingAttrChanged"},requireAll = false)
public static void setListener(RatingBar view,
final RatingBar.OnRatingBarChangeListener listener,
final InverseBindingListener attrListener) {
if (attrListener == null) {
view.setOnRatingBarChangeListener(listener);
} else {
//view->model
view.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
@Override
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
if (listener != null) {
listener.onRatingChanged(ratingBar, rating, fromUser);
}
attrListener.onChange();
}
});
}
}
}
这段代码定义了RatingBar类的android:rating属性,由于没有定义event和method属性,所以event的属性默认为android:ratingAttrChanged,method的默认属性对应为getRating。其实RatingBar类已经提供了android:rating属性,这里为什么要重新定义?其实是为了防止双向绑定死循环的问题。当我们在布局中使用了databinding表达式,那么就会调用到setRating()方法,判断当前值和设置的值是否相同,避免循环设置。
下面说一下重点方法setListener():
这里面的BindingAdapter多了两个属性
- android:onRatingChanged
- android:ratingAttrChanged
另外我们知道,这两个属性和setListener方法的参数是按照顺序对应的,所以android:onRatingChanged对应OnRatingBarChangeListener ,android:ratingAttrChanged对应InverseBindingListener 。而我们又知道,android:ratingAttrChanged对应 @InverseBindingMethod的event属性,所以最终event转换为InverseBindingListener。上面说到event属性用来双向绑定时对view的变化作出通知的 ,所以最终试通过InverseBindingListener实现通知的,对应即 onChange() 方法
六、使用
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="rating"
type="Float" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RatingBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numStars="10"
android:rating="@={rating}"
/>
</LinearLayout>
</layout>
注意使用 @= 表达式。
activity中绑定
final ActivityInverseBindingMethodsBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_inverse_binding_methods);
binding.setRating(5f);
Log.e("m---->view",binding.getRating()+"");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.e("view---->m",binding.getRating()+"");
}
},5000);
初始值为5,然后滑动控件,5s后发现控件变化: