【版權申明】非商業目的註明出處可自由轉載
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/106308380
出自:shusheng007
系列:
秒懂Android開發之DataBinding詳解 (part 1)
秒懂Android開發之DataBinding詳解 (part 2)
概述
在秒懂Android開發之DataBinding詳解 (part 1)一文中我們已經瞭解並上手了DataBinding,意味着你可以幹活了。 接下來就該看看其綁定規則及 BindingAdapter了,學習了DataBinding而不理解綁定原理及BindingAdapter的話,只能算是學到了皮毛。
後面行文中以DB作爲DataBinding的縮寫, BA作爲BindingAdapter的縮寫
DataBinding的綁定規則
你是否想過我們在layout
文件中對TextView使用了綁定表達式後,系統是如何爲其賦值的呢?以及我們可以修改TextView的哪些特性呢?例如字體顏色可以使用表達式嗎?字體大小呢?背景可以嗎?文字超出Textview寬度後的結束方式可以嗎?等等這些問題都應該搞清楚,正是因爲這些原理性的知識將程序員分爲了庸俗和優秀兩類。不理解原理,一旦遇到特殊需求或者異常情況就會顯得束手無策。
綁定原理
DataBinding通過自動生成代碼的方式幫助程序員省去了賦值操作,整體生成的代碼比較多,最核心的是那個綁定類的具體實現類。例如我的layout
文件生成綁定類爲ActivityDataBindingBinding
,那麼核心類爲其子類ActivityDataBindingBindingImpl
。
有興趣的應該研究一下這個生成代碼的實現,由於是插件生成的,可讀性稍差,但仍然是可以閱讀的。
綁定規則
綁定規則分3種情況
- 由類庫按規則自動選擇綁定方法
此方式系統會嘗試調用那個屬性的setter方法,例如我們爲Textview的透明度賦值 android:alpha=“@{xxx}”
那麼類庫就會尋找Textview裏面的setAlpha(arg)。如果TextView裏面有好幾個setAlpha的重載,由表達式xxx
的返回類型確定調用哪一個,所以這個表達式返回值的類值型很重要。
只要是Android Framwork 提供了的屬性都可以直接使用data binding 的賦值表達式,這裏說的屬性就是你可以在layout
文件中使用的那些。
data binding甚至可以綁定View中那些不存在對應屬性的setter方法。 例如DrawerLayout
中存在 setScrimColor(int)
這樣一個set方法,但是不存在可以在layout
中使用的屬性,所以沒有DB的時候我們只能從代碼中調用,不能在layout
文件中設置。但是使用DB就可以在layout
文件中進行綁定了,如下代碼所示。
<androidx.drawerlayout.widget.DrawerLayout
...
app:scrimColor="@{xxx}"/>
- 自定義綁定方法名稱
從第一條我們瞭解到,系統會去找屬性對應的setter
方法, 那如果屬性和對應的setter方法沒有遵循傳統格式怎麼辦呢?例如 Textview的一個屬性叫 autoLink
但是其對應的設置方法叫setAutoLinkMask()
而不是叫setAutoLink()
,那系統就找不到對應的設置方法了,那怎麼辦呢?
使用@BindingMethod
註解來對其重命名, 這個註解可以放在任何類上面,一般是放在你正在處理的類上面。下面的代碼將綁定方法重命名了,通過這個註解系統就知道給autoLink
賦值時調用setAutoLinkMask()
而不是setAutoLink()
方法
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
...
})
public class TextViewBindingAdapter {}
對於Android Framwork的屬性,即以android:
開頭的屬性都不用管,DB已經幫忙做好了。但是如果你自己寫了一個擴展Textview的自定義View, 自定義屬性發生了這種問題,你就需要按照上面的方法處理了。所以你就不要找不自在了,老老實實的按照setter的語法書寫!
- 自定義賦值方法名稱和邏輯
終於到了BindingAdapter出場的時候了。如果一個View的屬性沒有Setter方法怎麼辦呢?或者你想爲一個View新加一個自定義屬性怎麼辦呢?BindingAdapter 爲此而生。
例如 android:paddingLeft
屬性就不存在相應的setter方法,那我們就可以使用BA使其可以在layout
文件中綁定。Android Framework 已經爲我們實現了這個綁定適配器,如下代碼所示:
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
明確了上面的綁定規則,寫起代碼來也就能做到心中有數了。 接下來就讓我們近距離觀察一下BindingAdatper
吧。
BindingAdatper
通過前面的閱讀,相應你已經清楚BindingAdatper就是DataBiding庫用來爲layout
中View設置值的,包括屬性和事件,的一種補充機制, 其最有魅力之處在於可以自定義名稱和邏輯。
例如下面代碼中對 android:text
值的綁定就是使用了DataBinding預先實現好的BindingAdapter。這裏需要提一下的是如果自定義BA與前的綁定規則衝突,那麼後者勝!
<TextView
android:id="@+id/tv_girl"
...
android:text="@{viewModel.targetGirl.name}"
app:wrapWithSymbol='@{viewModel.symbol}'/>
DataBinding預先爲很多View實現了各種BindingAdapter,這些你可以從你project的External Libaries裏面的
androidx.databinding:databinding-adapters:xxx
找到,如下圖所示:
我們簡單來看一下TextViewBindingAdapter
,這個是DataBinding 爲TextView
實現的BindingAdapter
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
...
ew.setText(text);
}
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
可以看到對應Textview的text
屬性有兩個BindingAdapter,一個正向的,一個反向的,反向的用於雙向綁定,這個後面再說。layout
文件中TextView 的text
屬性的綁定表達式就是由上面那個正向BindingAdapter負責賦值的。
通過BindingAdapter,我們可以爲某個View使用自定義的屬性,例如前面例子中的 app:wrapWithSymbol='@{viewModel.symbol}'
就是我給Textview自定義的一個屬性,這個綁定適配器的功能給人的感覺就和Kotlin裏面的擴展函數似的,只是其擴展的對象是各種View.
如何寫一個BindingAdapter
又到了躬行的時候了,不然終覺淺,讓我們動手寫一個簡單的BA吧。其實很簡單,可以分爲3步:
- 寫一個
public static
方法。 - 確定方法入參,第一個入參爲要綁定的View類型,例如Textview。第二個參數爲要綁定的屬性的值
- 使用
@BindingAdapter
註解標記,其參數爲自定義屬性名稱。名稱可以是任意字符串,不要帶命名空間,例如app:wrapWithSymbol
這種方式是不提倡的,因爲在查找BA的時候命名空間是會被忽略的,還一堆警告。
完成以上3步後一個BindingAdapter就寫好了,當然了,你要在方法體裏面寫上邏輯。舉個例子?sure!
先上一個Java版本吧,比較容易理解
public class MyDataBindingAdapters {
@BindingAdapter("wrapWithSymbol")
public static wrapWithSymbol(TextView view, String symbol) {
view.text = symbol+view.getText()+symbol;
}
}
對應的kotlin版本如下,稍微複雜一點,其中@JvmStatic
就是爲了讓kotlin編譯器產生靜態函數的。
object MyDataBindingAdapters {
@JvmStatic
@BindingAdapter("wrapWithSymbol")
fun wrapWithSymbol(view: TextView, symbol: String) {
view.text = "$symbol${view.text}$symbol"
}
}
BindingAdapter 裏的參數wrapWithSymbol
就是我們在layout文件中要使用的屬性名稱,可以任意定義。 方法的入參很重要,DB通過入參的類型去匹配BindingAdapter 。
多屬性BindingAdapter
自定義BindingAdapter本來就不是太常用,用到的話大多也是單屬性的,然而查看@BindingAdapter
註解可以發現,它是允許傳入多個值的。
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();
boolean requireAll() default true;
}
第一個方法類型爲數組,第二個爲bool,表示數組中傳入的屬性是否都必須賦值。
例如我們可以定義如下一個包含兩個屬性的BindingAdapter
@JvmStatic
@BindingAdapter(value=["imageUrl", "error"],requireAll = true)
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
可以在layout
文件中按照如下方式調用
<ImageView
...
app:imageUrl="@{viewModel.imageUrl}"
app:error="@{@drawable/venueError}" />
因爲我們設置了requireAll = true
,所以在layout
中這兩個屬性都必須賦值.
至此本文應該結束了,但是我們稍微擴展一下事件的綁定,如果沒有興趣的就可以散場了。
事件綁定
前面我們一直在談論屬性的賦值,沒有具體談論對事件的綁定,DB綁定事件時有兩種方式:
- Method references
這種方式要求我們的業務邏輯類裏的方法簽名必須與對應的Listener
裏面的方法在返回值和入參上保持簽名一致。例如View的OnClickListener源碼如下
public interface OnClickListener {
void onClick(View v);
}
那麼我們的handler方法簽名必須與Listener裏的onCLick方法一致,即返回類型爲void
,入參爲1個View類型
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
這樣我們就可以在layout
文件中以這種方式綁定事件了,如下代碼所示
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
</data>
<LinearLayout
...
>
<TextView
...
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
這種方式在事件進行綁定的時候系統已經爲我們生成了相應Listener的實現了。
- Listener bindings
這種方式就比較靈活了,相應Listener的實現類在事件觸發的時候才生成。
只要求處理類的方法返回值類型與對應Listener
一致即可,例如還是綁定View 的Click事件,我們的處理類中的方法可以傳入任意個數和類型的參數了
class MyHandlers {
fun onClickFriend(girl: Girl) { ... }
}
我們可以使用如下綁定
<data>
<variable name="girl" type="com.android.example.Girl" />
<variable name="handlers" type="com.example.MyHandlers"/>
</data>
<Button
...
android:onClick="@{() -> handlers.onClickFriend(girl)}" />
OnClickListener 裏onClick(View v);的參數v
可以省略,也可以不省略,但是要求:要不全傳,要不全省。
<Button
...
android:onClick="@{(v) -> handlers.onClickFriend(girl)}" />
之所以講上面兩種事件綁定方式,主要是想談論一下如何綁定非函數式接口的監聽,換句話說,Listener裏面不止一個方法。例如View 中有這麼一個接口,它有兩個方法,那麼就不能直接綁定,怎麼辦呢?
/**
* Interface definition for a callback to be invoked when this view is attached
* or detached from its window.
*/
public interface OnAttachStateChangeListener {
public void onViewAttachedToWindow(View v);
public void onViewDetachedFromWindow(View v);
}
答案是使用BindingAdapter,但即使使用BA也是不容易的,需要按照如下方式處理:
- 將原來的接口拆成多個接口,每個方法對應一個。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
- 使用BindingAdapter多屬性方式處理
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
...
}
- 綁定
<TextView
...
android:onViewDetachedFromWindow="@{() -> handlers.onClickFriend()}"/>
總結
不知不覺嘚嘚了這麼多,我將自己認爲正確打開DataBinding的那些事都儘量說清楚了,但正所謂師父領進門修行在個人,就像俺高中時候一個老師教育俺們的:“考清華北大的學生根本就不是教出來的,而是自己學出來的”。其實他說的很對,因爲他自己只是山西臨汾師範學院畢業的。
so,加油吧,少年!
下一篇就是收宮之篇了,我們談一下雙向綁定。
本文源碼:AndroidDevMemo