banner
jzman

jzman

Coding、思考、自觉。
github

BindingAdapter in Android Jetpack Components

PS: The past is like smoke; one must learn to analyze life and not stumble in the same place twice. In the years to come, change is necessary, and one must take initiative, with a determined and enterprising heart, avoiding aimlessness throughout the day.

The previous article mainly covered the basic usage of DataBinding. The series of articles on Android Jetpack components is as follows:

This article mainly introduces the usage of Binding adapters, with the following content:

  1. DataBinding mechanism
  2. BindingMethods
  3. BindingAdapter
  4. BindingConversion

DataBinding Mechanism#

Binding adapters can be used as a framework for setting a certain value. The DataBinding library allows specifying specific methods for setting related values, where some processing logic can be applied. Binding adapters will ultimately give you the desired result. So how do we call the corresponding property methods when using DataBinding to bind data in the layout file?

 <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.name}" />

When binding a certain data in the layout file, such as the text property of the above TextView, it will automatically receive the corresponding method for compatible type parameters, like setText(arg). At this time, the DataBinding library will look for the method user.setName(arg) that corresponds to the return type of user.getName(). If the return type of user.getName() is a string, it will call the setName(arg) method with a String parameter; conversely, if it is an int type, it will call the setName(arg) method with an Int parameter. Therefore, to ensure data correctness, it is best to ensure the correctness of the return value in the XML expression, although type conversion can also be performed as needed.

From the above analysis, it can be seen that when a property is set in the layout file, the DataBinding library will automatically look for the relevant setter method to set it. In other words, if we take TextView as an example, as long as a certain setter method is found, it can be verified. TextView has a setError(error) method as follows:

@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);
    }
}

This method is mainly used to prompt error messages, which we generally use in code. Here, we configure this method to be used in the layout file, as shown below:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.name,default=name}"
    app:error="@{user.name}"/>

Below is a test effect image:

image

Because there is a setError(String error) method, and user.name returns a String, it can be configured here as a property.

BindingMethods#

This is an annotation provided by the DataBinding library, used to map when a certain property in a View does not correspond to its setter method name. For example, the TextView property android corresponds to the method setHintTextColor. At this time, the property name does not match the corresponding setter method name, so the BindingMethods annotation is needed to bind the property to the corresponding setter method. This way, DataBinding can find the corresponding setter method according to the property value. DataBinding has already handled cases where properties in native Views do not match setter methods. Let's take a look at how these mismatched properties are handled in the source code of 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 {
    //...
}

Therefore, for some properties in the View of the Android framework, the DataBinding library has already used BindingMethods to perform automatic property matching. So how do we customize setter methods when certain properties do not have corresponding setter methods while using DataBinding? At this point, we need to use BindingAdapter.

BindingAdapter#

  • Property Setting Preprocessing

When certain properties require custom processing logic, BindingAdapter can be used. For example, we can redefine the setText method of TextView in BindingAdapter to convert all input English letters to lowercase. The custom TextViewAdapter is as follows:

/**
 * Custom BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/6 0006.
 */
public class TextViewAdapter {

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        // Omitted special processing...
        String txt = text.toString().toLowerCase();
        view.setText(txt);
    }
}

At this time, when we use DataBinding, we prioritize using our own defined BindingAdapter. You may wonder how it can be recognized. During the compilation period, the data-binding compiler will look for methods annotated with @BindingAdapter, and ultimately the custom setter methods will be generated into the corresponding binding class. The generated part of the code is as follows:

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    // batch finished
    if ((dirtyFlags & 0x2L) != 0) {
        // api target 1
        // Note: This is the custom TextViewAdapter
        com.manu.databindsample.activity.bindingmethods.TextViewAdapter.setText(this.tvData, "This is TextView");
    }
}

Below is a case to verify the usage of BindingAdapter. Create the layout file as follows:

<?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">
        <!-- Default TextView -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#a37c7c"
            android:text="This is TextView..."
            android:textSize="16sp" />
        <!-- TextView using DataBinding -->
        <TextView
            android:id="@+id/tvData"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:background="#a37c7c"
            android:text="@{`This is TextView...`}"
            android:textSize="16sp" />
    </LinearLayout>
</layout>

The effect of using the custom BindingAdapter is as follows:

image

It can be seen that the custom TextViewAdapter is effective, allowing for convenient special processing of data as needed, which is also the purpose of BindingAdapter.

  • Custom Property Setting

Custom property settings can define a single property or multiple properties. Let's first define a single property, as shown below:

/**
 * Custom BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ImageViewAdapter {
    /**
     * Define a single property
     * @param view
     * @param url
     */
    @BindingAdapter("imageUrl")
    public static void setImageUrl(ImageView view, String url) {
        Glide.with(view).load(url).into(view);
    }
}

At this time, we can use the custom property imageUrl in the layout file, as shown below:

<?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">
        <!-- Custom single property -->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/>
    </LinearLayout>
</layout>

The test effect of the above code is as follows:

image

This allows for convenient loading of network images using the imageUrl property. There is no need to worry about thread switching issues, as the DataBinding library will automatically handle thread switching. Now, how do we customize multiple properties?

Below is how to define multiple properties:

/**
 * Custom BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ImageViewAdapter {
    /**
     * Define multiple properties
     * @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);
    }
}

At this time, the layout file can use the three properties defined above, namely imageUrl, placeholder, and error, as shown below:

<?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">
        <!-- Custom multiple properties -->
        <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>

At this time, all three properties must be used for the BindingAdapter to work properly. If only some of the properties are used, it will not compile successfully. So how can we use some properties when customizing multiple properties? The @BindingAdapter annotation has another parameter, requireAll, which defaults to true, indicating that all properties must be used. Setting it to false allows for normal use of partial properties. At this time, when customizing multiple properties, the requireAll attribute of the @BindAdapter annotation should be set to false, as shown below:

// 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);
}

At this time, the layout file can use partial properties, such as the following layout file that only uses imageUrl and placeholder without causing compilation errors:

 <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}"/>

The introduction to BindingAdapter ends here.

BindingConversion#

In some cases, when setting properties, type conversion between types is necessary. At this point, the @BindingConversion annotation can be used to complete the conversion between types. For example, the android property accepts a Drawable, and when we set a color value in the DataBinding expression, @BindingConversion is needed. Create the layout file as follows:

<?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">
        <!-- Type conversion -->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@{true ? @color/colorRed : @color/colorBlue}"/>
    </LinearLayout>
</layout>

Use @BindingConversion for type conversion, as shown below:

/**
 * Type conversion
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ColorConversion {
    @BindingConversion
    public static ColorDrawable colorToDrawable(int color) {
        return new ColorDrawable(color);
    }
}

The test effect of the above code is as follows:

image

When using the @BindingConversion annotation, the same type must be used. For example, the android property cannot be used like this:

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

Whether it is BindingAdapter or BindingConversion, they will ultimately generate the relevant code into the corresponding binding class, and then set its value to the specified View. This concludes the introduction to BindingMethods, BindingAdapter, and BindingConversion.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.