PS:往事如煙,要懂得剖析生活,不要在同一個地方摔倒兩次,此後餘年,需改變,更需主動,要篤定進取之心,勿恍恍而終日。
上篇主要是 DataBinding 的基本使用,Android Jetpack 組件系列文章如下:
- Android Jetpack 組件之 Lifecycle 篇
- Android Jetpack 組件之 LiveData 篇
- Android Jetpack 組件之 ViewModel 篇
- Android Jetpack 組件之 DataBinding 篇
本篇文章主要介紹 Binding adapters 的使用方式,內容如下:
- databinding 機制
- BindingMethods
- BindingAdapter
- 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}"/>
下面是測試效果圖:
因為有 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 效果如下:
可知,自定義的 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 屬性接收的是一個 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 屬性不能這樣使用:
<!--類型轉換-->
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@{true ? @color/colorRed : @drawable/drawableBlue}"/>
不管是 BindingAdapter 還是 BindingConversion 最終都會將相關代碼生成到與之對應的 binding 類中,然後在將其值設置給指定的 View,到此為止,BindingMethods 、BindingAdapter 和 BingingConversion 的相關知識就介紹到這。