iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
0
Mobile Development

Android Architecture Components 學習心得筆記系列 第 14

Day 14 Data Binding (Last) 雙向綁定 InverseBindingAdapter

  • 分享至 

  • xImage
  •  

雙向綁定 InverseBindingAdapter

之前我們做的都是 Data Binding 的單向綁定,ViewModel 一有變化就去通知 View 做更新,但是 View 有變化卻不會去通知 ViewModel(Ex.使用者在輸入框輸入文字)。
舉例來說:

<EditText
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:text="@{viewModel.content}" />

<TextView
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:text="@{viewModel.content}" />

有一個 EditTextTextView 他們的 text 都是去抓 viewModel.content
但是我在 EditText 打字時卻不會去更新 viewModel.content 做改變,TextView 就不會更新。

要完成雙向綁定其實很簡單,只要把 EditText 改成

<EditText
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:text="@={viewModel.content}" />

在大括號前面加一個等於就可以了,現在只要 EditText 的 text 一有變化就會去通知 viewModel.content 做改變,而 viewModel.content 一改變,ViewModel 就會去通知 TextView 更新,達到 EditTextTextView 同步的效果。

當然不會所有事都這麼簡單,這邊就來做一個客製化的雙向綁定。
這邊有兩個 Spinner,第二個 Spinner 的內容會跟著第一個 Spinner 的選擇做改變

@BindingAdapter("spinnerList")
fun setSpinnerList(spinner: Spinner, contentList: ObservableField<List<String>>) {

    val adapter = ArrayAdapter(
        spinner.context,
        android.R.layout.simple_spinner_dropdown_item,
        contentList.get()!!
    )
    spinner.adapter = adapter
}

@BindingAdapter("selectionPosition")
fun setSpinnerSelectionPosition(spinner: Spinner, selectPosition: Int) {
    spinner.setSelection(selectPosition)
}

這邊和一般的綁定一樣,用自定義的 BindingAdapter 把 List 丟給 Spinner,
只是第一個 Spinner 的選擇要跟 ViewModel 雙向綁定,用雙向綁定就會需要一個 getter
這時候可以用 @InverseBindingAdapter 來完成這件事

@InverseBindingAdapter(attribute = "selectionPosition")
fun getSpinnerSelectionPosition(spinner: Spinner): Int {
    return spinner.selectedItemPosition
}

這裡的 attribute 要跟 setter 的 key 對應,這時候 Databinding 會產生一個 {Attribute name}+AttrChanged 的 BindingAdapter value 來獲取數據改變的時機,因為這是 Spinner,所以就在 onItemSelected裡面去呼叫onChange()

@BindingAdapter("selectionPositionAttrChanged")
fun setSpinnerOnChangeListener(spinner: Spinner, listener: InverseBindingListener) {
    spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {

        override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            listener.onChange()
        }

        override fun onNothingSelected(parent: AdapterView<*>?) {
        }
    }
}

最後再把 Spinner 與 ViewModel 綁定,記得 selectionPosition 的 @ 後面要加上 =

        <Spinner
            selectionPosition="@={viewModel.selectedPosition}"
            spinnerList="@{viewModel.parentList}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <Spinner
            spinnerList="@{viewModel.subList}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp" />

這樣就大功告成囉,附上完整程式碼

Data Binding 小結

Data Binding 算是一個全新的概念,把先前寫在 Java 檔的東西都搬到了 xml 去做,一開始在玩的時候覺得真的蠻酷的,但是要去做一些比較複雜的事情時才開始體會到 Data Binding 的坑真的蠻多的,像是

  • 要 debug 時,Databinding 顯示的錯誤 Log 不會告訴我是哪一行,必須要自己去檢查,甚至還有這種超出版面的 Log
    https://ithelp.ithome.com.tw/upload/images/20190825/20119398LB34FedVdX.png
    得要整串複製下來貼在別的地方才看得到,蠻不方便的

  • 沒有 AutoComplete (自動補全),在 xml 的程式碼幾乎都要每個字自己打出來,不像 Java 檔一個單字打前面一兩個字母就會有很多建議選單跑出來,所以常常打錯一個字母就要找錯找很久,無形中也浪費了很多 debug 的時間。

雖然坑很多,但是只用一些基本的功能還是挺方便的,而且這算是蠻新的組件,也期許谷歌把拔可以再對 Data Binding 做一些優化,把 Data Binding 提升到另一個境界。

有任何問題或講得不清楚的地方歡迎留言和我討論。

更歡迎留言糾正我任何說錯的地方!

下一篇:LiveData 介紹與使用


上一篇
Day 13 Data Binding (六) RecyclerView
下一篇
Day 15 LiveData 介紹與使用
系列文
Android Architecture Components 學習心得筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言