PS:原文首发于微信公众号:躬行之(jzman-blog)

上篇主要是 DataBinding 的基本使用,Android Jetpack 组件系列文章如下 :

  • Android Jetpack组件之Lifecycle篇
  • Android Jetpack组件之LiveData详解
  • Android Jetpack组件之ViewModel篇
  • Android Jetpack组件之DataBinding详解

本篇文章主要介绍 Binding adapters 的使用方式,内容如下:

  1. databinding机制
  2. BindingMethods
  3. BindingAdapter
  4. 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 的相关知识就介绍到这。

更多相关文章

  1. Android(安卓)异步开发之 AsyncQueryHandler
  2. 关于Layout
  3. android中对程序进行数字证书签名的方法
  4. android studio IDE 下,设置ACTIVITY全屏
  5. Android自定义数字键盘解析
  6. LayoutInflater的作用和使用方法
  7. 谈谈动画架构?
  8. ReactNative学习----20ReactNative中调用原生模块
  9. Android(安卓)View状态保存

随机推荐

  1. NDK的Windwos环境搭建
  2. android SDK中java环境变量配置
  3. Andfix使用说明
  4. framework 开发 之 mmssms.db 中表结构的
  5. Android多国语言的value文件夹命名方式
  6. Android之ImageView载入网络上的图片
  7. android studio 常用的快捷键
  8. android下libgdx 中文字符显示初探
  9. Android(安卓)优质技术分享
  10. 在Ubuntu下搭建Android(安卓)SDK开发环境