繫結轉接器負責執行適當的架構呼叫以設定值。例如設定屬性值 (例如呼叫 setText()
方法)。另一個範例是設定事件監聽器,例如呼叫 setOnClickListener()
方法。
資料繫結程式庫可讓您指定呼叫的方法,以設定值、提供自己的繫結邏輯,以及使用轉接程式指定傳回的物件類型。
設定屬性值
每當繫結值變更時,產生的繫結類別都必須使用繫結運算式在檢視畫面上呼叫 setter 方法。您可以讓資料繫結程式庫自動決定方法,也可以明確宣告方法,或提供自訂邏輯以選取方法。
自動選取方法
針對名為 example
的屬性,程式庫會自動尋找接受相容類型做為引數的 setExample(arg)
方法。系統不會考慮屬性的命名空間。搜尋方法時,系統只會使用屬性名稱和類型。
例如,假設 android:text="@{user.name}"
運算式,程式庫會尋找 setText(arg)
方法,且該方法接受 user.getName()
傳回的類型。如果 user.getName()
的傳回類型為 String
,程式庫會尋找接受 String
引數的 setText()
方法。如果運算式傳回 int
,程式庫會搜尋接受 int
引數的 setText()
方法。運算式必須傳回正確的類型。您可以視需要轉換傳回的值。
即使沒有任何屬性符合指定名稱,資料繫結仍會運作。您可以使用資料繫結為任何 setter 建立屬性。舉例來說,支援類別 DrawerLayout
沒有屬性,但具有大量 setter。下列版面配置會自動分別使用 setScrimColor(int)
和 addDrawerListener(DrawerListener)
方法做為 app:scrimColor
和 app:drawerListener
屬性的 setter:
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
指定自訂方法名稱
部分屬性的 setter 與名稱不符。在這些情況下,您可以使用 BindingMethods
註解,將屬性與 setter 建立關聯。這個註解會與類別搭配使用,可包含多個 BindingMethod
註解,每個重新命名的方法各一個。繫結方法是一種註解,可以加入應用程式的任何類別。
在以下範例中,android:tint
屬性會與 setImageTintList(ColorStateList)
方法建立關聯,而不是與 setTint()
方法建立關聯:
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
一般來說,您不需要在 Android 架構類別中重新命名 setter。系統已採用名稱慣例實作屬性,以便自動尋找相符的方法。
提供自訂邏輯
部分屬性需要自訂繫結邏輯。例如,android:paddingLeft
屬性沒有相關聯的 setter。而是改為提供 setPadding(left,
top, right, bottom)
方法。帶有 BindingAdapter
註解的靜態繫結轉接器方法可讓您自訂呼叫屬性的 setter 方式。
Android 架構類別的屬性已有 BindingAdapter
註解。以下範例顯示 paddingLeft
屬性的繫結轉接器:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
參數類型相當重要。第一個參數會決定與屬性相關聯的檢視畫面類型。第二個參數會決定指定屬性的繫結運算式接受的類型。
繫結轉接器也可用於其他類型的自訂。例如,您可以透過工作站執行緒呼叫自訂載入器來載入圖片。
您也可以使用可接收多個屬性的轉接程式,如以下範例所示:
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
您可以在版面配置中使用轉接程式,如以下範例所示。請注意,@drawable/venueError
是指應用程式中的資源,用 @{}
括住資源可使其成為有效的繫結運算式。
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
如果 ImageView
物件使用 imageUrl
和 error
,imageUrl
是字串,而 error
是 Drawable
,系統就會呼叫轉接程式。如果您想在設定任一屬性時呼叫轉接程式,請將轉接程式的選用 requireAll
標記設為 false
,如以下範例所示:
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
繫結轉接器方法可在自己的處理常式中採用舊值。採用新舊值的方法必須先宣告屬性的所有舊值,接著才是新值,如以下範例所示:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } }
事件處理常式只能與具有一種抽象方法的介面或抽象類別搭配使用,如以下範例所示:
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@BindingAdapter("android:onLayoutChange") public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } } }
請在版面配置中使用此事件處理常式,如下所示:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
如果事件監聽器有多個方法,則必須分割為多個事件監聽器。舉例來說,View.OnAttachStateChangeListener
有兩個方法:onViewAttachedToWindow(View)
和 onViewDetachedFromWindow(View)
。程式庫提供兩個介面,以便區分這些介面的屬性和處理常式:
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
由於變更一個事件監聽器可能會影響兩者,因此您需要適用於上述任一屬性或兩者的轉接程式。您可以將註解中的 requireAll
設為 false
,指定並非每個屬性都必須指派繫結運算式,如以下範例所示:
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
上述範例稍微複雜,因為 View
類別使用 addOnAttachStateChangeListener()
和 removeOnAttachStateChangeListener()
方法,而非 OnAttachStateChangeListener
的 setter 方法。android.databinding.adapters.ListenerUtil
類別可協助您追蹤這些事件監聽器,以便在繫結轉接器中移除這些事件監聽器。
物件轉換
自動轉換物件
從繫結運算式傳回 Object
時,程式庫會選取用來設定屬性值的方法。Object
會轉換為所選方法的參數類型。如果應用程式使用 ObservableMap
類別儲存資料,這項行為就很方便,如以下範例所示:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
運算式中的 userMap
物件會傳回一個值,該值會自動轉換為用於設定 android:text
屬性值的 setText(CharSequence)
方法中找到的參數類型。如果參數類型不明確,請在運算式中轉換傳回類型。
自訂轉換
在某些情況下,您必須進行特定類型的自訂轉換。例如,檢視畫面的 android:background
屬性預期的是 Drawable
,但指定的 color
值是整數。以下範例是預期的屬性,其中預期的是 Drawable
,但改為提供整數:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
每當預期有 Drawable
且傳回整數時,請將 int
轉換為 ColorDrawable
。如要執行轉換,請使用具有 BindingConversion
註解的靜態方法,如下所示:
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
不過,繫結運算式提供的值類型必須一致。您無法在同一個運算式中使用不同類型,如以下範例所示:
// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
其他資源
如要進一步瞭解資料繫結,請參閱下列資源。
範例
程式碼研究室
網誌文章
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 資料繫結程式庫
- 版面配置與繫結的運算式
- 檢視區塊繫結