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 的相關知識就介紹到這。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。