# Android筆記--DataBinding
前置閱讀: [Android筆記(kotlin)–ViewBinding(視圖綁定)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_viewBinding)
DataBinding 是 Android 提供的一個工具,可以讓 XML 中的 UI 元素直接綁定到程式碼中的資料物件。這能==大幅簡化 UI 的更新邏輯,減少繁瑣的 findViewById() 操作並且可以在資料變化時,UI 自動更新(尤其搭配 `LiveData` 或 `Observable`)==,提高程式碼的可讀性與維護性。
<u>**`DataBinding` 和 `ViewBinding` 在某些功能上有重疊,但它們的設計目標與使用場景不同**</u>:
| 特性 | DataBinding | ViewBinding |
| --- | --- | --- |
| **目標** | 提供強大的資料與 UI 綁定功能,支援 MVVM 架構。 | 提供簡化的 `findViewById` 替代方案。 |
| **資料綁定能力** | 支援資料綁定,直接將資料對應到 UI。 | 不支援資料綁定,只處理 XML 元素引用。 |
| **雙向綁定** | 支援雙向資料綁定,適合用於表單輸入等情境。 | 不支援雙向綁定。 |
| **語法** | 需要在 XML 中加入表達式(如 `@{}`)。 | 無需改動 XML,只生成與 XML 對應的綁定類。 |
| **適用場景** | - 需要資料驅動 UI 時<br/>- 使用 MVVM 架構時 | - 僅需操作 View<br/>- 簡單 UI 互動場景 |
依賴:
---
[※==參考==※](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_viewBinding?stext=962%3A5%3A0%3A1734060166%3Au9gAu7)
在app層級的`build.gradle.kts`中開啟即可(和ViewBinding一樣)
```kotlin=
android {
...
buildFeatures {
dataBinding true
}
}
//或是
android {
...
dataBinding {
enable = true
}
}
```
簡單使用:
---
用DataBinding讓一個TextView和ViewModel關連並綁定,使VM內的值有變化時,TextView也會自動更新
- <u>**步驟1**</u><span id="portalA"></span>.到要使用DataBinding的布局文件中,對最外層的Layout按下`Alt+Enter`,選擇`convert to data binding layout`,系統就會將布局文件轉換成DataBinding的樣式。
| 轉換前 | 轉換後 |
| -------- | -------- |
|  | |
- <u>**步驟2**</u>.在轉換後新出現的`<data>`區塊中新增`<variable>`,在`<variable>`中宣告要使用到的變數名、類別的全路徑,需填寫兩個值`name`和`type`
- `name`: 這個class在這個 xml 的命名
- `type`: 這個class的所在位置,填上 class 完整的 package name,就能夠在這裡使用這個 class 的 public 變數
```kotlin=
<data>
<variable
name="myViewModel"
type="com.example.mypratice_databinding_2.MyViewModel" />
</data>
```

- <u>**步驟3**</u>.將要使用DataBinding的地方,替換成`@{}`,內部呼叫要使用的變數,例如:`myViewModel的text_1`
```kotlin=
android:text="@{myViewModel.text_1}"
```

<u>**備註**</u>:使用`@{}`的View,在預覽視窗中文字會顯示空白,是正常現象,可以改用`android:text="@{myViewModel.text_1, default = XXX}"`,就會文字了
- <u>**步驟4**</u>.在要使用DataBinding的程式碼中聲明Binding及viewModel(如果有使用到VM的話)
聲明binding的方法和ViewBinding一樣,如果同時使用ViewBinding和DataBinding的話,只要聲明一次即可,不需要額外操作。
```kotlin=
binding = ActivityMainBinding.inflate(layoutInflater)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
```
- <u>**步驟5**</u>.將 ViewModel (viewModel) 綁定到 binding,以便在 XML 檔案中可以直接使用 ViewModel 的資料和方法
```kotlin=
binding.myViewModel = viewModel
```

- <u>**步驟6**</u>.聲明`lifecycleOwner`
`lifecycleOwner` 是為了讓資料繫結系統能感知 Activity 的生命週期,從而監聽 LiveData 的變化並自動更新 UI。
```kotlin=
//在Activity中聲明lifecycleOwner用this
binding.lifecycleOwner = this
//在fragment中聲明lifecycleOwner用viewLifecycleOwner
binding.lifecycleOwner = viewLifecycleOwner
```
<br/>
<u>**至此,我們的佈局中的TextView已經能依照ViewModel中的值的變化自動更新了。**</u>
[GitHub專案](##GitHUb專案:)
進階:
---
==警告: 過度或花式使用DataBinding可能會導致代碼可讀性降低、測試困難等後果==
:::spoiler 取代`findViewByID()`
類似於[ViewBinding](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_viewBinding),DataBinding也可以取代`findViewByID()`來對視圖進行引用
- 步驟1.將布局文件轉換成DataBinding的樣式。[參考](#portalA)
- 步驟2-1.在Activity裡,使用`DataBindingUtil.setContentView()`
```kotlin=
//在Activity裡的話,使用
binding = DataBindingUtil.setContentView(requireActivity(), R.layout.fragment_bmi_history)
```
- 步驟2-2.在Fragment裡的話,和[viewBinding一樣](https://hackmd.io/VqYnjlp8R6qotXik1gUnPg?view#%E2%80%BB%E5%9C%A8Fragment%E4%B8%AD%E4%BD%BF%E7%94%A8ViewBinding%E2%80%BB)使用`binding.inflate()`
```kotlin=
//範例
class BmiHistory : Fragment() {
private lateinit var binding: FragmentBmiHistoryBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentBmiHistoryBinding.inflate(inflater, container, false)
return binding.root
}
}
```
:::
:::spoiler 雙向綁定: 資料從VM流向UI,同時,資料從 UI 流向 ViewModel,即 ViewModel ↔ UI。
- 基本版的DataBinding我們使用的`@{}`是單向綁定,也就是VM變時UI會跟著更新,但是反過來,UI變時VM中的值不會跟著更新,是單向的。
- 雙向綁定則是使用`@={}`,當VM變時UI會跟著更新,且UI更新時VM也會跟著跟新,是雙向的。<br/>例如:當使用者在 EditText 中輸入內容,例如輸入 World,viewModel.editText 的值會自動更新為 World。
- 適合用在表單輸入、搜尋框等需要即時回傳使用者輸入的場景。
```xml=
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.editText}" />
```
:::
:::spoiler 格式化輸出: 在使用DataBinding時使用反引號「\`」進行字串模板格式化輸出,透過反引號來建立字串模板。
例如說,我們想要輸出「Name: AAA」,並且AAA使用dataBinding連結ViewModel的話,就要這樣寫:
```kotlin=
//A寫法
android:text="@{`Name: ${myViewModel.name}`}"
//B寫法
android:text="@{`Name:` + myViewModel.name}"
```
:::
:::spoiler 將方法用DataBinding綁定
- 直接綁定: 當按鈕被點擊時,直接呼叫ViewModel中的onButtonClick()方法
`android:onClick="@{() -> viewModel.onButtonClick()}"`
- 帶參數的方法綁定: 當EditText文字變更時,呼叫ViewModel中的onTextChanged()方法,並傳遞文字參數
`android:onTextChanged="@{(text, start, before, count) -> viewModel.onTextChanged(text)}"`
```kotlin=
//在XML中
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="點擊我"
android:onClick="@{() -> viewModel.onButtonClick()}" />
<!-- 單向綁定:監聽文字變更 -->
<!-- 備註:這裡只是示範多參數傳遞的方法,實際上用到的參數只有text一個 -->
<EditText
android:id="@+id/singleWayBindingEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onTextChanged="@{(text, start, before, count) -> viewModel.onTextChanged(text)}"
android:hint="單向綁定"
/>
<!-- 雙向綁定 -->
<EditText
android:id="@+id/twoWayBindingEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.inputText}"
android:hint="雙向綁定"
/>
```
```kotlin=
//在ViewModel中
class MyViewModel2:ViewModel() {
// 單向綁定的消息
private val _message = MutableLiveData<String>()
val message: LiveData<String> = _message
fun onButtonClick() {
// 按鈕點擊事件可以同時修改兩種方式的值
_message.value = "按鈕已被點擊!"
}
fun onTextChanged(text: CharSequence?) {
// 單向綁定的文字變更處理
_message.value = "單向綁定文字: ${text.toString()}"
}
// 雙向綁定的文本
val inputText = MutableLiveData<String>().apply {
value = "" // 初始值
}
}
```
```kotlin=
//在Activity中
viewModel.inputText.observe(this) { text ->
Toast.makeText(this, "當前文字: $text", Toast.LENGTH_SHORT).show()
}
```
:::
:::spoiler 串接xml字串資源
以下將示範四種串接法
- 前置
```kotlin=
// User.kt (數據模型)
data class User(
val name: String,
val age: Int,
val points: Int
)
// ViewModel (MainViewModel.kt)
class MainViewModel : ViewModel() {
val user = User(
name = "張三",
age = 30,
points = 150
)
}
```
- 基本字串插值
```kotlin=
//資源
<resources>
<string name="welcome_message">歡迎, %1$s!</string>
</resources>
//布局文件
<!-- 基本字串資源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/welcome_message(user.name)}" />
//輸出結果
//歡迎, 張三!
```
- 多參數字串
```kotlin=
<resources>
<string name="user_info">姓名: %1$s, 年齡: %2$d</string>
<resources>
<!-- 多參數字串資源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/user_info(user.name, user.age)}" />
//輸出結果
//姓名: 張三, 年齡: 30
```
- 數字格式化
```kotlin=
<resources>
<string name="points_format">目前積分: %1$d 分</string>
<resources>
<!-- 帶數字的字串資源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/points_format(user.points)}" />
//輸出結果
//目前積分: 150 分
```
- 條件字串顯示
```kotlin=
<resources>
<string name="high_points">積分達標,獲得獎勵!</string>
<string name="low_points">繼續加油,積分有待提升</string>
<resources>
<!-- 條件顯示字串資源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.points > 100 ? @string/high_points : @string/low_points}" />
//輸出結果(當前積分150,大於設定值100,所以顯示@string/high_points)
//積分達標,獲得獎勵!
```
:::
:::spoiler 型別轉換
- 數字型別轉換為字串: 使用 String.valueOf() 將數字轉換為字串
```kotlin=
android:text="@{String.valueOf(user.id)}"
android:text="@{String.valueOf(user.age)}"
```
- 浮點數格式化: 使用 String.format() 控制小數點位數,(.1f 表示保留一位小數)
```kotlin=
android:text="@{String.format(`身高: %.1f 公分`, user.height)}"
```
- 自定義型別轉換: 在ViewModel中定義轉換方法
```kotlin=
//根據年齡劃分不同階段
// 定義數據模型
data class User(
val id: Long,
val name: String,
val age: Int,
val height: Float,
val isActive: Boolean
)
// ViewModel (MainViewModel.kt)
class MainViewModel : ViewModel() {
// 自定義型別轉換方法
fun convertAgeToLevel(age: Int): String {
return when {
age < 18 -> "青少年"
age in 18..35 -> "青年"
age in 36..55 -> "中年"
else -> "老年"
}
}
}
<!-- 自定義型別轉換 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.convertAgeToLevel(user.age)}" />
```
:::
:::spoiler 布林值轉換: 使用三元運算符根據布林值顯示不同文字
```kotlin=
android:text="@{user.isActive ? `使用者已啟用` : `使用者未啟用`}"
//等同於
if (user.isActive) {
"使用者已啟用"
} else {
"使用者未啟用"
}
```
:::
:::spoiler 空值處理
```kotlin=
android:text="@{user.name ?? `未知使用者`}"
//user.name 是非 null,則使用 user.name 的值
//如果 user.name 是 null,則使用 ?? 後面的值(即 未知使用者)。
```
:::
Q&A
---
<font size=4>**1.如果要在Fragment中使用DataBinding,應該在Fragment還是Activity的Layout加上[<data>...</data>](#portalA)?</font>**
:::spoiler 解答
如果要在Fragment中使用DataBinding,那應該要在Fragment的Layout中將佈局文件修該為`<data></data>`而不是修改所依附Activity的佈局文件,因為Fragment 的 View 可以在 onDestroyView() 時被銷毀,但 Activity 的 View 不會,這會導致潛在的 Memory Leak。
:::
踩坑紀錄
---
### Q:如果在把Databinding綁定的檔案移動到新的 package 中,並在 XML 中使用 DataBinding 綁定時即使提供了正確的路徑可能會出現錯誤
例如,當我們在XML檔案中進行以下綁定
```xml=
<data>
<variable
name="viewModel"
type="com.example.task_15.task_fragment.TaskViewModel" />
</data>
```
在這個情況下,如果出現了下列兩種錯誤:
```
//錯誤1
e:Cannot access class 'TaskFragment.TaskViewModel'. Check your module classpath for missing or conflicting dependencies.
//錯誤2
e: Assignment type mismatch: actual type is 'com.example.task_15.TaskFragment.TaskViewModel', but 'TaskFragment.TaskViewModel?' was expected.
```
這時,「「請檢查Package的命名是否為全小寫」」,將package的名稱改為小寫即可解決,==因為如果包名中包含大寫字母,可能會導致 DataBinding 無法正確生成綁定類。==

GitHUb專案:
---
- 基本版:[MyPratice_Databinding_1](https://github.com/PudCheetah/MyPratice_Databinding_1)
- 多重參數傳遞:[MyPratice_Databinding_3](https://github.com/PudCheetah/MyPratice_Databinding_3)
參考資料:
---
- [AndroidDeveloper--数据绑定库](https://developer.android.com/topic/libraries/data-binding?hl=zh-cn)
- [AndroidDeveloper--Android 中的数据绑定](https://developers.google.com/codelabs/android-databinding?hl=zh-cn#0)
- ★ [Android DataBinding 从入门到进阶](https://juejin.cn/post/6844903609079971854)
- [YT--DataBinding in Android](https://youtu.be/h135uamWlbg?si=19slLAU075AwUX2c)
- [Day 8 Data Binding (一) 介紹與基本使用](https://ithelp.ithome.com.tw/m/articles/10219729)
- [Kotlin + Android Databinding 那一兩件事](https://medium.com/jastzeonic/kotlin-android-databinding-%E9%82%A3%E4%B8%80%E5%85%A9%E4%BB%B6%E4%BA%8B-5d223466615a)
- [Data Binding Library 實作 MVVM Pattern 那兩三件事情](https://medium.com/jastzeonic/data-binding-library-%E5%AF%A6%E4%BD%9C-mvvm-pattern-%E9%82%A3%E5%85%A9%E4%B8%89%E4%BB%B6%E4%BA%8B%E6%83%85-1d6e0de66aaf)
- ★ [DataBinding,让人又爱又恨的一个框架](https://juejin.cn/post/6857815150565687303)
- [犹豫要不要用DataBinding?这篇文章帮你解惑](https://blog.csdn.net/c10WTiybQ1Ye3/article/details/106934714)