PS: the original text was first published on wechat official account: Jingxing zhi (Jzman-blog)

The Jetpack component series for Android is as follows:

  • Lifecycle for Android Jetpack components
  • LiveData for Android Jetpack
  • ViewModel for Android Jetpack components
  • Android Jetpack component DataBinding details

This article mainly introduces how to use Binding Adapters as follows:

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

Databinding mechanism

Binding Adapters can be used as a framework to set a value. The Databinding library allows you to specify a specific method to set the value. In this method, you can do some processing logic. So how do we call the corresponding property methods when we use databinding binding data in a layout file?

 <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@{user.name}" />


Copy the code

When binding data to a layout file, such as the text property of the TextView above, the binding automatically receives methods for arguments of compatible types, such as setText(ARG), The Databinding library looks for the user.setName(arg) method that receives the user.getName() return type. If user.getName() returns a string, The setName(arg) method of String is called, whereas the setName(arg) method of int is called. So, to ensure that the data is correct, try to ensure that the value returned in the XML expression is correct. Of course, You can also perform type conversions as needed.

If you set a property in a layout file, the Databinding library automatically looks for a setter method to set it. If you set a property in a TextView, for example, you need to find a setter method to validate it. TextView has a setError(error) method like this:

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

    }

}

Copy the code

This method is mainly used to prompt error messages, which are usually used in code. Here we configure this method to be used in the layout file, as follows:

<TextView

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@{user.name,default=name}"

    app:error="@{user.name}"/>


Copy the code

Below is the test effect:

jzman-blog

Because of the setError(String error) method and the String returned by user.name, this can be configured as an attribute here.

BindingMethods

This is an annotation provided by the Databinding library to map a property in a View to its corresponding setter method name, For example, android:textColorHint is the same method as setHintTextColor. In this case, the name of the property is inconsistent with the name of the corresponding setter method. This requires binding the property to the corresponding setter method using the BindingMethods annotation so that databinding can find the corresponding setter method based on the property value. Databinding already handles cases where properties like this do not match setter methods in a native View.

@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 {

    / /...

}

Copy the code

So, for some properties in the View in the Android framework, the Databinding library has already done automatic attribute lookup matching using BindingMethods, so when there is no setter method for some properties, How do you customize setter methods when using databinding? That’s when you use BindingAdapter.

BindingAdapter

  • Property setting preprocessing

We can use BindingAdapter when we need to customize the processing logic for certain attributes. For example, we can use BindingAdapter to redefine the setText method of TextView to convert all the English input to lowercase. Customize TextViewAdapter 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) {

        // omit special processing...

        String txt = text.toString().toLowerCase();

        view.setText(txt);

    }

}

Copy the code

At this point, when we use databinding to use our own BindingAdapter first, we might wonder why we can recognize it. During compilation, the data-binding compiler looks for methods with @bindingAdapter annotations, The custom setter method is eventually generated into the corresponding Binding class, with the following code generated:

@Override

protected void executeBindings(a) {

    long dirtyFlags = 0;

    synchronized(this) {

        dirtyFlags = mDirtyFlags;

        mDirtyFlags = 0;

    }

    // batch finished

    if ((dirtyFlags & 0x2L) != 0) {

        // api target 1

        // Note: this is a custom TextViewAdapter

        com.manu.databindsample.activity.bindingmethods.TextViewAdapter.setText(this.tvData, "This is a TextView");

    }

}

Copy the code

To verify the use of BindingAdapter as an example, create a layout file as follows:


       

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


        <! - the default TextView -- -- >

        <TextView

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="#a37c7c"

            android:text="This is a TextView..."

            android:textSize="16sp" />


        <! -- Use 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 is this... `}"

            android:textSize="16sp" />


    </LinearLayout>

</layout>

Copy the code

Using a custom BindingAdapter looks like this:

jzman-blog

TextViewAdapter (BindingAdapter); TextViewAdapter (BindingAdapter);

  • Custom property Settings

Custom attribute Settings can define a single attribute or multiple attributes. To define a single attribute, see the following:

/ * *

* Custom BindingAdapters

 * Powered by jzman.

 * Created on 2018/12/7 0007.

* /


public class ImageViewAdapter {

    / * *

* Define a single attribute

     * @param view

     * @param url

* /


    @BindingAdapter("imageUrl")

    public static void setImageUrl(ImageView view, String url) {

        Glide.with(view).load(url).into(view);

    }

}

Copy the code

At this point we can use the custom attribute imageUrl in the layout file as follows:


       

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


        <! Customize a single attribute -->

        <ImageView

            android:layout_width="100dp"

            android:layout_height="100dp"

            app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/>


    </LinearLayout>

</layout>

Copy the code

The above code test results are as follows:

jzman-blog

This makes it easy to use the imageUrl attribute to load web images. Don’t worry about thread switching. The Databinding library does this automatically.

You can customize multiple attributes as follows:

/ * *

* Custom BindingAdapters

 * Powered by jzman.

 * Created on 2018/12/7 0007.

* /


public class ImageViewAdapter {

    / * *

* Define multiple attributes

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

    }

}

Copy the code

At this point, you can use the three properties defined above in the layout file, namely imageUrl, placeholder, error, as follows:


       

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


        <! -- Customize multiple attributes -->

        <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>

Copy the code

If the BindingAdapter uses all three attributes, then the BindingAdapter will not work properly. If the BindingAdapter uses all three attributes, then the BindingAdapter will not work properly. The @bindingAdapter annotation also has a requireAll parameter. RequireAll defaults to true, indicating that all properties must be used. The requireAll attribute of @bindAdapter 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);

}

Copy the code

In this case, the layout file can use some attributes, such as imageUrl and placeholder, and there will be no 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}"/>


Copy the code

So much for the introduction of BindingAdapter.

BindingConversion

In some cases, conversion between types is required when a property is set. This can be done with the @bindingConversion annotation, For example, android:background accepts a Drawable. When we set a color value in databinding’s expression, we need @bindingConversion, creating the layout file as follows:


       

<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>

Copy the code

Type conversion using @bindingConversion as follows:

/ * *

* Type conversion you

 * Powered by jzman.

 * Created on 2018/12/7 0007.

* /


public class ColorConversion {

    @BindingConversion

    public static ColorDrawable colorToDrawable(int color){

        return new ColorDrawable(color);

    }

}

Copy the code

The above code test results are as follows:

jzman-blog

Use the same type when using @bindingConversion annotations. The Android :background attribute above cannot be used like this:

<! -- Type conversion -->

<ImageView

    android:layout_width="100dp"

    android:layout_height="100dp"

    android:background="@{true ? @color/colorRed : @drawable/drawableBlue}"/>


Copy the code

Either the BindingAdapter or the BindingConversion will eventually generate the relevant code into the corresponding Binding class, and then set its value to the specified View. So much for BindingMethods, BindingAdapter, and BingingConversion.