绑定适配器
随着DataBinding进一步学习,发现其实就是将一部分原本属于代码中的逻辑放到了布局中,并且封装了很多重复的操作,提高开发效率。如果不加以限制,原本简洁的布局会更加复杂。要做到恰到好处分担一些简单的业务处理,布局不要承担过重的业务逻辑。
今天学习一下DataBinding中一个强大的功能:BindingAdapter绑定适配器。在布局中通过UI属性的设置,完成图片加载、数据处理等复杂需求,一部分代码由布局分担,并且提高复用程度。通过该注解可以自定义属性,还可以覆盖系统原有的控件属性。
1、自定义BindingAdapter
演示如何自定义BindingAdapter适配器,结合Glide加载图片。
1.1、自定义BindingAdapter
通过@BindingAdapter注解自定义绑定适配器。
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView view, String url) {
Glide.with(view).load(url).into(view);
}1.2、使用自定义属性
在绑定布局中用 app:自定义属性 的方式使用自定义绑定适配器,绑定适配器负责发出相应的框架调用来设置值,调用上面的loadImage()方法加载图片:
<ImageView
app:imageUrl="@{url}" />暂时无需知道这其中复杂的注解、回调原理。绑定布局外层需要包含如下命名空间,才能调用到@BindingAdapter注解的静态方法:
xmlns:app="http://schemas.android.com/apk/res-auto"1.3、支持重载
支持重载,可以设置多个值:
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Glide.with(view).load(url).error(error).into(view);
}如果不是所有的值都需要可以设置requireAll = false,但是回调方法中就需要额外判断以免发生异常:
@BindingAdapter(value = {"imageUrl", "error"}, requireAll = false)
public static void loadImage(ImageView view, String url, Drawable error) {
//先判断值是否非空
}2、覆盖框架属性
框架给View预先设定了很多属性,比如text、size等。如果我们自定义的注解同名,该如何处理?实际上会覆盖框架的属性,这样开发者就有很大的操作空间。下面演示一下
2.1、自定义text适配器
用@BindingAdapter注解系统框架属性android:text
@BindingAdapter("android:text")
public static void setText(TextView textView, String text) {
textView.setText(text.toUpperCase() + " Quibbler");
}2.2、使用自定义框架属性
通过android:text属性给TextView设置文字:
<TextView
android:text="@{`I love my family, because I have a happy family.`}" />2.3、注意点
避免采坑,记录几点:
①注意得通过数据绑定语法的方式设置属性值@{},通过原始的布局语法方式设置是没有效果的:
<TextView
android:text="I love my family, because I have a happy family." />②我们在2.1节中定义的是android:text属性这个才是覆盖重写了系统属性。如果不加android: 前缀,直接定义text属性
@BindingAdapter("text")
public static void setText(TextView textView, String text) {
textView.setText(text.toUpperCase() + " Quibbler");
}则还是自定义的需要用app: 命名空间去访问才有效,这时候就得改成
<TextView
app:text="@{`I love my family, because I have a happy family.`}" />3、获取旧值
BindingAdapter还有一个功能点,就是能获取属性对应的旧值,在修改某个属性的时候希望根据上一个值来修正。
3.1、添加oldValue形参
继续以2.1节中的自定义BindingAdapter,增加一个参数oldText,修改TextView前的文字内容:
@BindingAdapter("text")
public static void setText(TextView textView, String oldText, String newText) {
textView.setText("old:" + oldText + "\nnew:" + newText);
}参数顺序不能错误,旧值必须在新值前。
3.2、改变
布局中定义变量string:
<variable
name="string"
type="String" />设置给TextView的自定义app:text属性:
<TextView
app:text="@{string}" />代码中改变变量string的值,注意不要直接改变TextView的内容而需要通过间接变量:
dataBindingBinding.setString(string);3.3、注意点
乐于尝试,不断采坑。每次开发都能遇到各种各样的坑非常郁闷,但是琢磨出来解决后又很释然。
这里通过BindingAdapter获取旧值也有一点需要注意,必须修改对应的变量才行。上面那个例子如果直接通过TextView的setText()方法修改TextView的文字,那么BindingAdapter是没有效果的。
//BindingAdapter不会响应
textView.setText(string);也就是整个操作都要在DataBinding体系内进行,Android原生的某些操作超出DataBinding有效范围。
4、类型转换
某些情况下,需要在特定类型之间进行自定义转换。比如将Color转换成Drawable,将Color转换成String等常见的转换场景,开发者在开发中一定没少遇到过。
DataBinding中另一个注解@BindingConversion对数据类型进行转换。下面通过简单的例子介绍一下
4.1、BindingConversion定义转换
通过@BindingConversion注解定义我们需要的类型转换。这里将布局绑定语法使用的Color全部转为Drawable:
@BindingConversion
public static Drawable convertToDrawable(Color color) {
return new ColorDrawable(color.toArgb());
}DataBinding将自动把布局文件中所有以@{Color}方式引用的全部转换成为Drawable,这个自动转换好用但是存在风险,所以用的时候一定要细心细心再细心。
4.2、是否一定会转换?
开发者一定在初期都踩过这个坑,直接通过TextView的setText()方法设置 int 类型的数字值,这时候肯定会报错。查看源码会发现setText()是重载函数,如果传入的参数类型是int,那么就会当成资源id去加载string资源。
public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
mTextSetFromXmlOrResourceId = true;
mTextId = resid;
}能不能用@BindingConversion注解,将布局中TextVIew用到的的int全部转为String呢?答案竟然是不能!经过一个上午的不断摸索尝试,发现真的不行:
如果属性对应的属性setter有重载,比如setText()方法,可以接受String、Integer等多种类型的参数,因此传入Integer转换不会发生因为刚好有对应的setter函数接受Integer类型的参数,即使我们定义的Integer到String的转换器:
@BindingConversion
public static String convertToString(Integer num) {
return num.toString();
}但是当实际却传了不支持的Color类型时,会查找是否有合适的@BindingConversion注解将Color转换成需要的String、Integed等类型。
比如TextView的setter方法没有setText(Color)这个重载,当我们传入的参数类型是Color时,就可以定义对应的转换器,将Color转换成String设置给TextView:
@BindingConversion
public static String convertToString(Color color) {
return color.toString();
}参考资料:

