Android(安卓)Jetpack组件之BindingAdapter详解
PS:原文首发于微信公众号:躬行之(jzman-blog)
上篇主要是 DataBinding 的基本使用,Android Jetpack 组件系列文章如下 :
- Android Jetpack组件之Lifecycle篇
- Android Jetpack组件之LiveData详解
- Android Jetpack组件之ViewModel篇
- Android Jetpack组件之DataBinding详解
本篇文章主要介绍 Binding adapters 的使用方式,内容如下:
- databinding机制
- BindingMethods
- BindingAdapter
- BindingConversion
databinding机制
Binding adapters 可以作为一个设置某个值的框架来使用,databinding 库可以允许指定具体的方法来进行相关值的设置,在该方法中可以做一些处理逻辑,Binding adapters 会最终给你想要的结果,那么当我们在布局文件中使用 databinding 绑定数据时是如何调用对应的属性方法呢?
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" />
当在布局文件中绑定某个数据时,比如上面的 TextView 的 text 属性,在绑定时会自动接收兼容类型的参数所对应的方法,如 setText(arg),此时 databinding 库会查找接收 user.getName() 返回类型对应的 user.setName(arg) 方法,如果 user.getName() 返回的类型是字符串,则会调用参数为 String 的 setName(arg) 方法,反之如果是 int 型,则会调用参数为 Int 的 setName(arg) 方法,所以,为了保证数据的正确性,尽量保证 xml 中表达式中返回值的正确性,当然,也可以按照实际需要进行类型转换。
从上面分析可知,在布局文件中设置了属性,databinding 库会自动查找相关的 setter 方法进行设置,也就是说,如果以 TextView 为例,只有找到某个 setter 方法就可以进行验证了,TextView 中有一个 setError(error) 方法如下:
@android.view.RemotableViewMethodpublic void setError(CharSequence error) { if (error == null) { setError(null, null); } else { Drawable dr = getContext().getDrawable( com.android.internal.R.drawable.indicator_input_error); dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); setError(error, dr); }}
这个方法主要用来提示错误信息,一般我们都是在代码中进行使用,这里我们把该方法配置到布局文件中来使用,参考如下:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name,default=name}" app:error="@{user.name}"/>
下面是测试效果图:
因为有 setError(String error) 方法,而 user.name 返回的时 String,所以能够在这里以属性的方式进行配置。
BindingMethods
这是 databinding 库提供的一个注解,用于当 View 中的某个属性与其对应的 setter 方法名称不对应时进行映射,如 TextView 的属性 android:textColorHint 与之作用相同的方法是 setHintTextColor 方法,此时属性名称与对应的 setter 方法名称不一致,这就需要使用 BindingMethods 注解将该属性与对应的 setter 方法绑定,这样 databinding 就能够按照属性值找到对应的 setter 方法了,databinding 已经处理了原生 View 中的像这种属性与 setter 方法不匹配的情况,来看一看源码中 TextView 中这些不匹配属性的处理,参考如下:
@BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"), @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"), @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"), @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),})public class TextViewBindingAdapter { //...}
所以,对于 Android 框架中 View 中的一些属性,databinding 库已经使用 BindingMethods 已经做了属性自动查找匹配,那么当某些属性没有与之对应的 setter 方法时,如何在使用 databinding 时自定义 setter 方法呢,此时就要使用 BindingAdapter 了。
BindingAdapter
- 属性设置预处理
当某些属性需要自定义处理逻辑的时候可以使用 BindingAdapter,比如我们可以使用 BindingAdapter 重新定义 TextView 的 setText 方法,让输入的英文全部转换为小写,自定义 TextViewAdapter 如下:
/** * 自定义BindingAdapters * Powered by jzman. * Created on 2018/12/6 0006. */public class TextViewAdapter { @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { //省略特殊处理... String txt = text.toString().toLowerCase(); view.setText(txt); }}
此时,当我们使用 databinding 的优先使用我们自己定义的 BindingAdapter,可能会疑惑为什么能够识别呢,在编译期间 data-binding 编译器会查找带有 @BindingAdapter 注解的方法,最终会将自定义的 setter 方法生成到与之对应的 binding 类中,生成的部分代码如下:
@Overrideprotected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } // batch finished if ((dirtyFlags & 0x2L) != 0) { // api target 1 //注意:这里是自定义的TextViewAdapter com.manu.databindsample.activity.bindingmethods.TextViewAdapter.setText(this.tvData, "这是TextView"); }}
下面以案例的形式验证一下 BindingAdapter 的使用,创建布局文件如下:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#a37c7c" android:text="这是TextView..." android:textSize="16sp" /> <TextView android:id="@+id/tvData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="#a37c7c" android:text="@{`这是TextView...`}" android:textSize="16sp" /> LinearLayout>layout>
使用自定义的 BindingAdapter 效果如下:
可知,自定义的 TextViewAdapter 生效了,可以根据需求很方便对一下数据进行预特殊处理,这也是 BindingAdapter 的作用。
- 自定义属性设置
自定义属性设置可以定义单个属性也可以定义多个属性,先来定义单个属性,参考如下:
/** * 自定义BindingAdapters * Powered by jzman. * Created on 2018/12/7 0007. */public class ImageViewAdapter { /** * 定义单个属性 * @param view * @param url */ @BindingAdapter("imageUrl") public static void setImageUrl(ImageView view, String url) { Glide.with(view).load(url).into(view); }}
此时我们可以在布局文件中使用自定义属性 imageUrl 了,使用参考如下:
<?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> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <ImageView android:layout_width="100dp" android:layout_height="100dp" app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/> LinearLayout>layout>
上述代码测试效果如下:
这样就可以很方便的使用 imageUrl 属性来加载网络图片了,这里不要担心线程切换问题,databinding 库会自动完成线程切换,那么如何自定义多个属性呢。
下面自定义多个属性,定义方式参考如下:
/** * 自定义BindingAdapters * Powered by jzman. * Created on 2018/12/7 0007. */public class ImageViewAdapter { /** * 定义多个属性 * @param view * @param url * @param placeholder * @param error */ @BindingAdapter(value = {"imageUrl", "placeholder", "error"}) public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) { RequestOptions options = new RequestOptions(); options.placeholder(placeholder); options.error(error); Glide.with(view).load(url).apply(options).into(view); }}
此时,可在布局文件中使用上面定义的三个属性了,即 imageUrl、placeholder、error,使用方式参考如下:
<?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> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <ImageView android:layout_width="100dp" android:layout_height="100dp" android:layout_marginTop="10dp" app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}" app:placeholder="@{@drawable/icon}" app:error="@{@drawable/error}"/> LinearLayout>layout>
此时,三个属性全部使用才能 BindingAdapter 才能正常工作,如果使用了其中的一些属性则不能正常编译通过,那么如何在自定义多个属性而正常使用其中的部分属性呢,@BindingAdapter 注解还有一个参数 requireAll ,requireAll 默认为 true,表示必须使用全部属性,将其设置为 false 就可以正常使用部分属性了,此时,自定义多个属性时要配置 注解 @BindAdapter 的 requireAll 属性为 false,参考如下:
// requireAll = false@BindingAdapter(value = {"imageUrl", "placeholder", "error"},requireAll = false)public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) { RequestOptions options = new RequestOptions(); options.placeholder(placeholder); options.error(error); Glide.with(view).load(url).apply(options).into(view);}
此时,布局文件就可以使用部分属性了,如下面布局文件只使用 imageUrl 和 placeholder 也不会出现编译错误:
<ImageView android:layout_width="100dp" android:layout_height="100dp" android:layout_marginTop="10dp" app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}" app:placeholder="@{@drawable/icon}"/>
BindingAdapter 的介绍到此为止。
BindingConversion
在某些情况下,在设置属性时类型之间必须进行转化,此时就可以借助注解 @BindingConversion 来完成类型之间的转换,比如 android:background 属性接收的是一个 Drawable 当我们在 databinding 的表达式中设置了一个颜色值,此时就需要 @BindingConversion,创建布局文件如下:
<?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> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <ImageView android:layout_width="100dp" android:layout_height="100dp" android:background="@{true ? @color/colorRed : @color/colorBlue}"/> LinearLayout>layout>
使用 @BindingConversion 进行类型转换,参考如下:
/** * 类型转换你 * Powered by jzman. * Created on 2018/12/7 0007. */public class ColorConversion { @BindingConversion public static ColorDrawable colorToDrawable(int color){ return new ColorDrawable(color); }}
上述代码测试效果如下:
使用 @BindingConversion 注解时要使用相同类型,如上面的 android:background 属性不能这样使用:
<ImageView android:layout_width="100dp" android:layout_height="100dp" android:background="@{true ? @color/colorRed : @drawable/drawableBlue}"/>
不管是 BindingAdapter 还是 BindingConversion 最终都会将相关代码生成到与之对应的 binding 类中,然后在将其值设置给指定的 View,到此为止,BindingMethods 、BindingAdapter 和 BingingConversion 的相关知识就介绍到这。
更多相关文章
- Android(安卓)异步开发之 AsyncQueryHandler
- 关于Layout
- android中对程序进行数字证书签名的方法
- android studio IDE 下,设置ACTIVITY全屏
- Android自定义数字键盘解析
- LayoutInflater的作用和使用方法
- 谈谈动画架构?
- ReactNative学习----20ReactNative中调用原生模块
- Android(安卓)View状态保存