本來想直接一章節大致講講完DataBinding的,但是要知道,DataBinding知識點還是蠻多的(我也還沒全部瞭解完)。加上我又喜歡寫的細一點,所以一篇寫下來洋洋灑灑一長篇,所以還是分開來寫吧!今天(好吧,其實和上一篇是同一天)我們要說的DataBinding常用的一些註解!
Android MVVM探索系列
Android MVVM探索(一) - DataBiding初解
Android MVVM探索(二) - DataBiding常用註解
Android MVVM探索(三) - ViewModel,DataBinding,LiveData混合三打
6, 一些常用註解說明
-
@BidingAdapter
在xml檔案中,總是有些控制元件的屬性不夠我們用,比如我們有一張圖片的網路地址,我們想直接在xml中將地址繫結到ImageView,讓它顯示這張圖片,很明顯的ImageView自帶的屬性明顯不能完成我們的要求。那怎麼辦呢?我們需要擴充屬性! 首先我們新建一個名為ViewBindingAdapters的Kotlin檔案,(如果是Java請新建一個普通類,類名隨意),內容如下:
package top.cyixlq.test import android.databinding.BindingAdapter import android.widget.ImageView import com.bumptech.glide.Glide @BindingAdapter("imgUrl") fun setImgUrl(view: ImageView, url: String) { Glide.with(view).load(url).into(view) } // Java如下: public class ViewBindingAdapters { @BindingAdapter("imgUrl") public static void setImgUrl(ImageView view, String url) { Glide.with(view).load(url).into(view) } } 複製程式碼
可以看到,我們使用了Glide,所以需要在app的build.gradle檔案中dependencies下加入(我寫這篇文章時,最新的Glide版本為4.8.0):
implementation 'com.github.bumptech.glide:glide:4.8.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 複製程式碼
我們需要在網上找了一張圖片的連結,並且把它加入到MainViewModel的屬性中:
val imgUrl = "這裡是圖片地址,地址太長,我就不貼在這裡"
我們在activity_main.xml檔案中加入一個ImageView,並使用我們擴充的屬性:
<ImageView android:layout_width="80dp" android:layout_height="80dp" imgUrl="@{viewModel.imgUrl}"/> 複製程式碼
最後別忘了在manifest中新增網路許可權:
<uses-permission android:name="android.permission.INTERNET" /> 複製程式碼
那麼,這個註解是如何判斷是給哪個控制元件新增的擴充屬性呢?請看本小節開頭我們建立的那個Kotlin檔案的內容,我們用@BindingAdapter註解修飾了一個setImgUrl的方法,註解中包含一個字串“imgUrl”。方法的第一個形參的型別是ImageView,所以,這個註解是通過第一個形參的型別來判斷擴充的是哪個控制元件的屬性,第一個形參也是設定了該擴充屬性的控制元件的例項。第二個形參傳進來的是一個String型別的值,也就是在xml佈局檔案中傳進來的viewModel.imgUrl,然後我們在方法體中進行一下處理即可實現自動載入網路圖片了!
@BindingAdapter註解需要傳入的第一個引數是字串陣列。如果你只想擴充一個屬性,那麼只傳一個字串就行。如果你想擴充多個屬性,那麼你就需要傳入一個字串陣列,一個字串就代表一個擴充屬性。當陣列長度大於一時,我們就需要設定它的第二個引數,requireAll(中文意思:全部必須)。這個引數為布林型。當為true時(預設就是為true),就像他的中文意思一樣,全部需要,代表你傳入的第一個引數,字串陣列的擴充屬性必須在一個控制元件上全部要設定,否則的話你想設定哪個擴充屬性就設定哪個擴充屬性!示例程式碼如下:
// 當為false時 @BindingAdapter(value = ["imgUrl", "bgRes"], requireAll = false) fun setImgUrl(view: ImageView, url: String, res: Int) { Glide.with(view).load(url).into(view) view.setBackgroundResource(res) } <!-- xml檔案程式碼 --> <ImageView android:layout_width="80dp" android:layout_height="80dp" imgUrl="@{viewModel.imgUrl}"/> // 當為true時 @BindingAdapter(value = ["imgUrl", "bgRes"], requireAll = true) fun setImgUrl(view: ImageView, url: String, res: Int) { Glide.with(view).load(url).into(view) view.setBackgroundResource(res) } <!-- xml檔案程式碼 --> <!-- 這段請放在data標籤中 --> <import type="top.cyixlq.test.R"/> <ImageView android:layout_width="80dp" android:layout_height="80dp" imgUrl="@{viewModel.imgUrl}" bgRes="@{R.mipmap.ic_launcher}"/> 複製程式碼
如果第二個引數設定為true但是在xml中有沒有將全部的擴充屬性設定好的話在編譯的時候就會報錯:
Found data binding errors.
補充一下,傳入的屬性值前面可以加上名稱空間,就像下面那樣(名稱空間有一定規範,最好是英文單詞):
@BindingAdapter(value = ["app:imgUrl", "app:bgRes"], requireAll = true) fun setImgUrl(view: ImageView, url: String, res: Int) { Glide.with(view).load(url).into(view) view.setBackgroundResource(res) } 複製程式碼
如果你加入了名稱空間,相應的你也需要在xml佈局檔案中引入名稱空間並且加上去,就像下面那樣:
<!-- 引入app名稱空間 --> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- 控制元件相應屬性加上名稱空間 --> <ImageView android:layout_width="80dp" android:layout_height="80dp" imgUrl="@{viewModel.imgUrl}" app:bgRes="@{R.mipmap.ic_launcher}"/> 複製程式碼
最後看看這個註解的原始碼,更方便理解:
@Target(ElementType.METHOD) public @interface BindingAdapter { String[] value(); boolean requireAll() default true; } 複製程式碼
-
@BindingConversion
這個註解的作用就是將不符合某個控制元件屬性的值的型別轉換成符合的型別。舉個例子:TextView的text屬性是需要String型別的,假如我們傳入時間戳(Long型別)很明顯是不行的,但是我就是不想自己每個都自己手動轉換一下,怎麼辦?那麼這個註解就派上用場了。我們直接上程式碼,就以TextView這個例子來說明:
1.我們新建一個Kotlin檔案,檔名為ViewBindingConversions,檔案內容如下:
@SuppressLint("SimpleDateFormat") @BindingConversion fun convertIntToString(value: Long): String { val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") return formatter.format(value) } 複製程式碼
- 把我們之前佈局檔案中新加入一個TextView,就像下面的程式碼,之後編譯執行,我們發現可以正常執行。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{System.currentTimeMillis()}"/> 複製程式碼
值得注意的是,這個註解,宣告一個,所有控制元件都會自動轉換。也就是說我們上面的那個方法,凡是某個屬性需要String型別的,如果你傳入了Long,它就會通過方法體中的程式碼進行轉換,最後填充的屬性值就是返回的結果。加入Button的text你也傳入了Long,那麼也會進行轉換,並不只限於TextView。用這個註解修飾的方法只有一個形參,它的型別代表是xml中傳進來的值的型別,最後函式必須要一個返回值,返回匹配控制元件支援的值得型別。所以感覺就是這個註解用起來不會太靈活!
-
@InverseMethod
在開發中,我們經常遇到某個字串,或者某個數字代表某種狀態。比如0代表女孩子,1代表男孩子。但是使用者選好男還是女之後,我們回傳到後臺的資料應該是0或1,但是我們不想手動轉換怎麼辦,那就用這個註解。這個註解的作用和上面那個註解@BindingConversion有點類似,但是不同的是,這個可以具體作用到某個控制元件例項上,還有雙向繫結的作用。如果不知道雙向繫結是什麼意思,請參照上一小章節。首先還是新建一個Kotlin檔案,檔名為:ViewInverseMethods,Java的話還是和上面一樣新建一個類,類名隨意。檔案內容如下:
// 這個註解引數為反轉的方法名,意味著一個這個註解需要兩個方法才能完成 @InverseMethod("sexToNum") fun numToSex(num: Int): String { return when (num) { 0 -> "女" 1 -> "男" else -> "未知性別" } } fun sexToNum(sex: String): Int { return when(sex) { "女" -> 0 "男" -> 1 else -> 2 } } 複製程式碼
是的,因為這個註解設計到雙向繫結,有轉換過去的方法肯定,肯定就有轉換回來的方法。我們在MainViewModel中新增屬性:
// 因為涉及雙向繫結,這裡必須是可觀察的資料型別 val num = ObservableInt(1) 複製程式碼
在佈局檔案中新增一個TextView和一個EditText,TextView用來觀察num這個值的變化,EditText用來展示轉換好之後的資料:
<!-- 這裡別忘了匯入,匯入了下面的EditText才能用這個類 --> <import type="top.cyixlq.test.ViewInverseMethodsKt"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(viewModel.num)}"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={ViewInverseMethodsKt.numToSex(viewModel.num)}"/> 複製程式碼
這時候編譯執行,EditText直接顯示了男,如果我們刪除男,EditText立馬顯示了未知性別。因為空字串會對應到sexToNum中的else,所以num的值瞬間變成2,而2又對應numToSex中的else,所以EditText就會顯示未知性別。當我們把未知性別幾個字全部刪除,輸入1,或者2也好,num都是2,因為字串-"1",字串-"2"都是對應sexToNum方法中的else。但是如果我們在輸入框中輸入男,num就變成1了,輸入女num就變成0了。其中的過程我就不繼續詳細解釋了。下面貼出一張動態圖: