PS: The past is like smoke; one must understand how to analyze life and not fall in the same place twice. In the years to come, change is necessary, and one must take the initiative, maintaining a determined and enterprising spirit, and not be lost in a daze throughout the day.
Previous Article mainly covers the basic usage of DataBinding. The series of articles on Android Jetpack components are as follows:
- Android Jetpack Component: Lifecycle
- Android Jetpack Component: LiveData
- Android Jetpack Component: ViewModel
- Android Jetpack Component: DataBinding
This article mainly introduces the usage of Binding adapters, with the following content:
- DataBinding Mechanism
- BindingMethods
- BindingAdapter
- BindingConversion
DataBinding Mechanism#
Binding adapters can be used as a framework to set a certain value. The DataBinding library allows specifying specific methods for setting related values, where some processing logic can be performed. 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 TextView above, it will automatically receive the corresponding method for compatible type parameters, such as setText(arg). At this time, the DataBinding library will look for the user.setName(arg) method corresponding to the return type of user.getName(). If the type returned by 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. Of course, 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 corresponding 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. The 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, and we generally use it 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:
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 for mapping when a certain property in the View does not correspond to its setter method name. For example, the TextView property android corresponds to the setHintTextColor method. In this case, the property name does not match the corresponding setter method name, so the BindingMethods annotation is needed to bind this 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 Android framework's View, 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 using 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, it will prioritize using our 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 generate the custom setter method 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 use of BindingAdapter, creating 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:
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. First, let's 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:
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 the definition of multiple properties, as shown:
/** * 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: 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 correctly. So how can we use some properties when customizing multiple properties? The @BindingAdapter annotation has another parameter, requireAll, which defaults to true, meaning 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, type conversion must be performed when setting properties. At this point, the @BindingConversion annotation can be used to complete the conversion between types. For example, the android property receives 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:
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 relevant code into the corresponding binding class, and then set their values to the specified View. This concludes the introduction to BindingMethods, BindingAdapter, and BindingConversion.