banner
jzman

jzman

Coding、思考、自觉。
github

Android Jetpack组件之BindingAdapter篇

PS:往事如烟,要懂得剖析生活,不要在同一个地方摔倒两次,此后余年,需改变,更需主动
,要笃定进取之心,勿恍恍而终日。

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

本篇文章主要介绍 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.RemotableViewMethod
public 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}"/>

下面是测试效果图:

image

因为有 setError (String error) 方法,而 user.name 返回的时 String,所以能够在这里以属性的方式进行配置。

BindingMethods#

这是 databinding 库提供的一个注解,用于当 View 中的某个属性与其对应的 setter 方法名称不对应时进行映射,如 TextView 的属性 android 与之作用相同的方法是 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 类中,生成的部分代码如下:

@Override
protected 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-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#a37c7c"
            android:text="这是TextView..."
            android:textSize="16sp" />
        <!--使用dataBinding的TextView-->
        <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 效果如下:

image

可知,自定义的 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>

上述代码测试效果如下:

image

这样就可以很方便的使用 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 属性接收的是一个 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);
    }
}

上述代码测试效果如下:

image

使用 @BindingConversion 注解时要使用相同类型,如上面的 android 属性不能这样使用:

<!--类型转换-->
<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@{true ? @color/colorRed : @drawable/drawableBlue}"/>

不管是 BindingAdapter 还是 BindingConversion 最终都会将相关代码生成到与之对应的 binding 类中,然后在将其值设置给指定的 View,到此为止,BindingMethods 、BindingAdapter 和 BingingConversion 的相关知识就介绍到这。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。