前言
本文是在前文的基礎上繼續深入DataBinding的使用這一塊,如果有不懂的地方,請移步上一篇
Android Jetpack之DataBinding(一)
Android Jetpack之DataBinding(二)
DataBinding使用之綁定適配器
綁定適配器負責發出相應的框架調用來設置值。如setText()、setOnClickListener()等,同時適配器綁定庫允許自定義方法、邏輯、和
返回值類型等操作。
1. 設置特性值、自動選擇方法:
只要綁定的值放生改變,生成的綁定類就會調用對應的set方法實現相應的改變,看個最簡單的例子如下,其實是在調用了響應的setText方法:
xml代碼:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
</data>
<LinearLayout
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_marginTop="@dimen/dimen_10"
android:id="@+id/tv_nick"
android:text="@{student.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginTop="@dimen/dimen_10"
android:id="@+id/tv_age"
android:text="@{student.age+``}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
class TestActivity7 : AppCompatActivity() {
var binding: ActivityTestDataBinding7Binding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding7
)
binding?.student = Student("小美女", 5)
}
}
效果如下:
綁定的過程分析:
- 起始點:起始的出發點肯定是在student變化的代碼,即:
binding?.student = Student("小美女", 5)
- 一路追進去最終會找到綁定實現類ActivityTestDataBinding7BindingImpl的方法executeBindings的方法如下,重點在下面
protected void executeBindings() {
....
//此處自動選擇get方法取值
// read student.name
studentName = student.getName();
// read student.age
studentAge = student.getAge();
...此處進行值的設置
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, studentAgeJavaLangString);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvNick, studentName);
...
}
最終調用到了TextViewBindingAdapter的setText方法,再進入可以看到具體的設置過程,可以看到在進行了一些列的判斷之後最終執行的方法是view的setText方法,和我們平時設置的過程是一樣的。
@BindingAdapter(“android:text”)
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don’t set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don’t set anything.
}
// 這裏
view.setText(text);
}
2. 綁定listener
...
<variable name="listener"
type="com.zgj.demojetpackapp.databinding.TestActivity7.EventListener"/>
<TextView
android:padding="10dp"
android:text="點擊事件"
android:onClick="@{listener::onClick}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
...
inner class EventListener {
fun onClick(view: View) {
Toast.makeText(this@TestActivity7, "點擊按鈕", Toast.LENGTH_SHORT).show()
}
}
...
binding?.listener = EventListener()
...
效果如下:
綁定的過程分析:
- 起始點:起始點依然是設置監聽器的位置
binding?.listener = EventListener()
- 和上述的綁定過程類似,最終到了ActivityTestDataBinding7BindingImpl的方法executeBindings的方法如下:
最終設置的是一個持有傳入listener的原生監聽器子類OnClickListenerImpl
protected void executeBindings() {
....
//聲明瞭一個View本身的listener
android.view.View.OnClickListener listenerOnClickAndroidViewViewOnClickListener = null;
//這塊是取到設置的listener
com.zgj.demojetpackapp.databinding.TestActivity7.EventListener listener = mListener;
...
//對上面聲明的變量原生listener進行賦值,三目運算符操作,是一個新的實現類OnClickListenerImpl
// read listener::onClick
listenerOnClickAndroidViewViewOnClickListener = (((mListenerOnClickAndroidViewViewOnClickListener == null) ? (mListenerOnClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mListenerOnClickAndroidViewViewOnClickListener).setValue(listener));
...
//最終將聲明的原生監聽器設置給setOnClickListener方法
this.mboundView3.setOnClickListener(listenerOnClickAndroidViewViewOnClickListener);
...
}
OnClickListenerImpl代碼如下:
// Listener Stub Implementations
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
//持有傳入的listener
private com.zgj.demojetpackapp.databinding.TestActivity7.EventListener value;
public OnClickListenerImpl setValue(com.zgj.demojetpackapp.databinding.TestActivity7.EventListener value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void onClick(android.view.View arg0) {
//此處調用傳入的listener的方法實現回調
this.value.onClick(arg0);
}
}
結果
此處可以看出listener具體綁定的實現,當然也可以發現前面說過的問題原因,
listener的監聽器的方法名和參數必須和原生的一樣(value.onClick(arg0))。
3. 提供自定義綁定方法:
此處不做過多的說明,一般來說系統的方法都能自動選擇實現綁定,可以看下TextViewBindingAdapter的源碼,
此處進行了自定義方法的綁定,如下:
@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"),
})
4. 提供自定義邏輯:
此處以加載圖片爲例,ImageView只有一個src設置圖片,沒有辦法根據特殊情況,如路徑問題、網絡問題導致的圖片加載不成功進行相關的
加載過程,一般來說項目中加載圖片會有網絡路徑、佔位圖和錯誤圖,這裏通過自定義邏輯實現這個過程。
<?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>
<variable name="url" type="String"/>
</data>
<LinearLayout
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_img"
app:url="@{url}"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
</layout>
class TestActivity7 : AppCompatActivity() {
var binding: ActivityTestDataBinding7Binding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding7
)
binding?.student = Student("小美女", 5)
binding?.listener = EventListener()
val url =
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1589877841442&di=55353d36cb62454230a0b86e90bd0c36&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F2018-11-06%2F5be15b7635e5d.jpg"
binding?.imgBean = ImgBean(url, R.mipmap.error, R.mipmap.placeholder)
}
companion object {
@BindingAdapter(value = ["url", "error", "placeholder"], requireAll = false)
@JvmStatic
fun loadImg(view: ImageView, url: String, err: Int?, placeholder: Int?) {
Glide.with(view.context).load(url).also {
placeholder?.let { it1 -> it.placeholder(it1) }
err?.let { it1 -> it.error(it1) }
}.into(view)
}
}
}
效果如下(第一個網絡圖加載成功,第二個只有佔位圖(展示佔位圖),第三個只有錯誤圖(或者錯誤圖佔位圖都有,這兩種都是展示錯誤圖)):
注意問題:
- kotlin中加載方法要寫在 伴生對象companion object {}內,同時加上@JvmStatic註解纔可以正常運行,如果不加註解則會報錯如下(提示不是靜態的,類似於java的靜態方法):
- xml中使用的參數名爲 @BindingAdapter(value = [“url”, “error”, “placeholder”], requireAll = false)中生命的參數名,不是方法的形參名,形參順序和其對應就可以,requireAll表示參數是否必須。
- 可選參數在方法形參必須聲明可空,即?。
5. 自動對象轉換:
當聲明或者返回的對象爲Object類型時候,會選擇設置值的方法進行設置,Object會轉換爲所選方法的參數類型,例如:
...
<variable name="info" type="Object"/>
...
<TextView
android:text="@{info}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
//注意此處類型,不能直接寫個1 那樣會報類型轉換錯誤
binding?.info ="123456789"
效果如下:
轉換的位置是在ActivityTestDataBinding7BindingImpl的executeBindings方法中,代碼如下:
protected void executeBindings() {
...
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView5, (java.lang.CharSequence) info);
...
}
5. 自定義轉換:
自定義轉換這塊,這塊提供兩個示例,一個是boolean轉換drawable,一個是Date轉換String,代碼如下:
...
<variable name="isException" type="boolean"/>
<variable name="date" type="java.util.Date"/>
...
<TextView
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:text="展示內容"
android:textColor="@android:color/white"
android:background="@{isException? @android:color/holo_red_dark: @android:color/darker_gray}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:text="展示內容1"
android:textColor="@android:color/white"
android:background="@{isException}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{date}"
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
//轉換器單獨定義類 @JvmStatic表示生成靜態方法
object BindingConverts {
@BindingConversion
@JvmStatic
fun convertDateToString(date: Date): String {
return SimpleDateFormat.getDateInstance(0).format(date)
}
@BindingConversion
@JvmStatic
fun convertBooleanToDrawable(back: Boolean): Drawable {
return if (back) {
ColorDrawable(Color.RED)
} else {
ColorDrawable(Color.DKGRAY)
}
}
}
展示效果如下(三個TextView對應三個效果,剛開始前兩個背景爲灰色,2s之後會變色,因爲延時2s修改了isException的值):
過程分析
- isException? @android:color/holo_red_dark: @android:color/darker_gray此處雖然傳入的是資源id,但是其實最終傳入的還是Drawable對象,只是這一步是系統的轉換器完成的,在ActivityTestDataBinding7BindingImpl的executeBindings方法中有如下代碼:
protected void executeBindings() {
...
androidx.databinding.adapters.ViewBindingAdapter.setBackground(this.mboundView6, androidx.databinding.adapters.Converters.convertColorToDrawable(isExceptionMboundView6AndroidColorHoloRedDarkMboundView6AndroidColorDarkerGray))
...
}
最終調用了Converters的convertColorToDrawable的方法,找到該方法如下,很清楚很明白,還是轉換器,只是系統已經定義好了
public class Converters {
/**
* Converts {@code int} color into a {@link ColorDrawable}.
*
* @param color The integer representation of the color.
*
* @return ColorDrawable matching the color
*/
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
/**
* Converts {@code int} color into a {@link ColorStateList}.
*
* @param color The integer representation of the color.
*
* @return ColorStateList from the single color
*/
@BindingConversion
public static ColorStateList convertColorToColorStateList(int color) {
return ColorStateList.valueOf(color);
}
}
- 其餘兩個的調用也是在ActivityTestDataBinding7BindingImpl的executeBindings方法中,如下:
protected void executeBindings() {
...
//boolean轉換Drawable
androidx.databinding.adapters.ViewBindingAdapter.setBackground(this.mboundView7, com.zgj.demojetpackapp.databinding.BindingConverts.convertBooleanToDrawable(isException));
...
//date轉換String
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView8, com.zgj.demojetpackapp.databinding.BindingConverts.convertDateToString(date));
...
}
注意問題:
-
報警告,其實報的很明白,這塊聲明isException爲Boolean類型爲裝箱類型,需要拆箱後進行操作,當然此處不處理運行也是正常的,將Boolean改成boolean就不再包警告了
警告: isException is a boxed field but needs to be un-boxed to execute isException ? @android:color/holo_red_dark :
@android:color/darker_gray. This may cause NPE so Data Binding will safely unbox it. You can change the expression
and explicitly wrap isException with safeUnbox() to prevent the warning -
自定義轉換器的時候注意將轉換器單獨寫在一個object聲明的類中,如BindingConverts ,不要寫在companion object 伴生對象中,如果寫進去會報錯如下:
提示你BindingConversion註解只能用在public static 方法上,但是其實此時也生成public static方法,我們通過查看對應的java代碼如下:
...
@JvmStatic
public static final void loadImg(@NotNull ImageView view, @NotNull String url, @Nullable Integer err, @Nullable Integer placeholder) {
Companion.loadImg(view, url, err, placeholder);
}
...
也生成了靜態方法,但是系統仍然報錯,報錯在伴生類的方法上
@BindingConversion
@JvmStatic
@NotNull
public final String convertDateToString(@NotNull Date date) {
Intrinsics.checkParameterIsNotNull(date, "date");
String var10000 = SimpleDateFormat.getDateInstance(0).format(date);
Intrinsics.checkExpressionValueIsNotNull(var10000, "SimpleDateFormat.getDateInstance(0).format(date)");
return var10000;
}
至於這個的具體原因目前還沒有探究清楚。
源碼
源碼鏈接:源碼
總結
1、databinding綁定適配器的使用及分析先到此處。
2、本以爲這塊適配器就是個小內容,準備分析完分析雙向綁定相關的內容,但是這塊的使用及坑還不少,限於篇幅,此篇先到此處結束,
後續再去分析雙向綁定相關的內容。
3、以上的代碼都是經過測試正常的,但是難免在粘貼或者修改的時候會有粗心或者錯誤,歡迎指出討論。
4、以上的注意問題都是自己踩過的坑,當然也可能由於各方面的侷限性會存在偏見,歡迎指出討論。
5、自勉,多研究多使用,深入其源碼往往會有意想不到的收穫,同時也更容易消化相關的只是與內容。