Android的DataBinding出来很久了,但是因为我一直用的mvp,出来的时候就简单的 开启了enable =true这句代码弄了下搞了个简单的layout布局就没管了(好吧,貌似只能算单向绑定=_=)。然后最近想深入的看下DataBinding,比如自定义的控件怎么实现双向绑定,一步一步来吧,其实东西真的不多,不过很强大,基本都是注解。
###简单的绑定
先建立一个Worker类:
public class Worker {
public int workerId;
public String name;
public int wage;
public String photoUrl;
public int photoId;
public Worker() {
}
public Worker(int workerId, String name, int wage, int photoId) {
this.workerId = workerId;
this.name = name;
this.wage = wage;
this.photoId = photoId;
}
}
这是一个简单的布局文件(data_binding_layout.xml),在TextView上显示Worker的名字:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="worker"
type="com.hiray.mvvm.mvvm.model.Worker" />
</data>
<TextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:text="@{worker.name}" />
</layout>
会根据布局文件名字生成一个ViewDataBinding对象————DataBindingLayoutBinding,在代码中设置一下:
public class DataBindingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.data_binding_layout);
binding.setWorker(new Worker(1205, "Mike", 12000, R.mipmap.boy);
}
}
以上是非常简单的一种绑定,只是单向的数据绑定,数据映射到UI上
关于这样的单向绑定有好几个注解
BindingMethods、BindingMethod
比如我想给ImageView加上自定义的属性下载图片
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="worker"
type="com.hiray.mvvm.mvvm.model.Worker" />
</data>
<ImageView
app:imageDrawable="@{worker.photoId}"
android:layout_width="50dp"
android:layout_height="50dp"/>
</layout>
这里”app:imageDrawable”这个属性是无法被识别的,会编译出错,我们要通过某种方式告诉系统该怎么做。我们建立一个类 ViewBindingAdapter,使用BindingMethods和BindingMethod注解:
@BindingMethods(
@BindingMethod(
type = ImageView.class,
attribute = "app:imageDrawable",
method = "setImageResource"
)
)
public class ImageAttr {
}
其中type是你要绑定的类,attribute是你自己定的,method是ImageView中的方法,也就是告诉ImageView,在xml中碰到app:imageDrawable属性的时候调用ImageView的setImageResource方法。
但是如果我此时不想传入图片id,而是传入一个id的String字符,那怎么办呢,因为ImageView并没有接收String参数来设置图片的方法,那么我们要利用BindConversion转换一下
BindingConversion
public class ImageAttr {
@BindingConversion
public static int convertStringToResId(String idString){
return Integer.parseInt(idString);
}
}
但是有些时候即使转换了,也没有对应的setter方法可以使用,比如你设置drawableLeft这种属性的时候,是没有setDrawableLeft方法的,只有setCompoundDrawable,那么这时候就可以使用 BindingAdapter这个注解了
BindingAdapter
顾名思义是绑定适配器,如果像设置的属性没有直接的方法,需要转换一下,那么就用到这个注解,比如设置drawableLeft,是没有setDrawableLeft方法的,必须调用view的setCompoundDrawables
public class ImageAttr {
@BindingAdapter("app:drawableLeft")
public static void bindDrawableLeft(TextView view, Drawable leftDrawable) {
view.setCompoundDrawables(leftDrawable, null, null, null);
}
}
InverseMethod
在双向绑定中,需要对值进行转换
比如我们有个checkbox,如果model中的一个String类型的值是“Alice”就让checkbox选上,反之不勾选,这里就要用到InverseMethod注解
新建一个Converter类,写了两个静态方法:
public class Converter {
@InverseMethod("convertStringToBool")
public static String convertBoolToString(boolean b) {
if (b)
return "Alice";
else return "Unknown";
}
public static boolean convertStringToBool(String name) {
return "Alice".equals(name);
}
}
布局文件:
<?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">
<data>
<variable
name="person"
type="com.hiray.mvvm.mvvm.model.Person"/>
<import type="com.hiray.mvvm.mvvm.attr.Converter"/>
</data>
<android.support.v7.widget.AppCompatCheckBox
android:checked="@={Converter.convertStringToBool(person.personName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</layout>
给自定义控件支持双向绑定
这里有个自定义的BlinkView,不停的闪烁,由一个bool 类型的值 blink控制是否闪烁,伪代码如下:
public class BlinkView extends View {
private Paint paint;
boolean blink = true;
......
public void setBlink(boolean blink) {
this.blink = blink;
invalidate();
}
public boolean getBlink(){
return blink;
}
public void toggle() {
setBlink(!blink);
if (onBlinkChangeListener != null)
onBlinkChangeListener.onBlinkChange(blink);
}
public interface OnBlinkChangeListener {
void onBlinkChange(boolean blink);
}
private OnBlinkChangeListener onBlinkChangeListener;
public void setOnBlinkChangeListener(OnBlinkChangeListener onBlinkChangeListener) {
this.onBlinkChangeListener = onBlinkChangeListener;
}
}
layout文件中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.hiray.mvvm.mvvm.model.DataHolder" />
</data>
<com.hiray.mvvm.mvvm.widget.BlinkView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:blink="@={data.blink}"
app:blinkColor="@color/colorAccent" />
</layout>
这样写显然不会有任何效果,建立一个名为BlinkViewAdapter的类,使用InverseBindingMethods注解告诉Android在遇到app:blink属性的时候怎么把数据映射到UI上,然后又要告诉AndroidUI怎么映射到数据上,代码如下:
@InverseBindingMethods(
@InverseBindingMethod(type = BlinkView.class,
attribute = "blink")
)
public class BlinkViewAdapter {
// @InverseBindingAdapter(attribute = "app:blink")
// public static boolean isBlink(BlinkView view) {
// return view.getBlink();
// }
@BindingAdapter(value = {"app:blinkChanged", "app:blinkAttrChanged"}, requireAll = false)
public static void setListener(BlinkView view, BlinkView.OnBlinkChangeListener listener,
final InverseBindingListener attrChange) {
if (attrChange == null && listener != null)
view.setOnBlinkChangeListener(listener);
else view.setOnBlinkChangeListener(new BlinkView.OnBlinkChangeListener() {
@Override
public void onBlinkChange(boolean blink) {
attrChange.onChange();
}
});
}
}
其中InverseBindingMethod注解有三个参数:type表示你要绑定的类,attribute就是你要进行绑定的属性(就是写在xml上的属性),method默认按照属性名字去找有没有 isXX 或者getXX方法,不然你就写上method名字,如果没有直接的method名字,可以使用InverseBindingAdapter注解(代码中注释的部分),这是告诉系统在UI发生变化的时候调用什么方法获取UI信息这里Android默认是按照属性名字去找有没有 xxAttrChanged的(如果你自己没有定义的话),当然这个event可以自己定义,比如你定义成”abcdefg&%##$”,那么上面setListener方法的BindingAdapter注解的”app:blinkAttrChanged”也得改成这个。
另外setListener有三个参数,第一个是控件BlinkView自己,第二个是BlinkView.OnBlinkChangeListener 和”app:blinkChanged”对应;第三个是InverseBindingListener是必须的,这个参数的实现在编译期就生成了,就是通知系统UI变化了,要更新ui信息到数据上(更新的方法就是前面的第一部分),可以看下生成的attrChange:
private android.databinding.InverseBindingListener mboundView3blinkAttrChange = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of data.blink
// is data.setBlink((boolean) callbackArg_0)
boolean callbackArg_0 = mboundView3.getBlink();
// localize variables for thread safety
// data.blink
boolean dataBlink = false;
// data != null
boolean dataJavaLangObjectNull = false;
// data
com.hiray.mvvm.mvvm.model.DataHolder data = mData;
dataJavaLangObjectNull = (data) != (null);
if (dataJavaLangObjectNull) {
data.setBlink(((boolean) (callbackArg_0)));
}
}
};
看到这里其实就明白了,这里生成的东西都是根据前面的注解来的,收到刷新提示就会调用方法获取UI信息,并且更新数据模型中的值,由此完成了整个的双向的绑定