前言
什么是Jetpack?
Jetpack 是一個(gè)由多個(gè)庫(kù)組成的套件,可幫助開(kāi)發(fā)者遵循最佳做法、減少樣板代碼并編寫(xiě)可在各種 Android 版本和設(shè)備中一致運(yùn)行的代碼,讓開(kāi)發(fā)者可將精力集中于真正重要的編碼工作
ViewModel,LiveData和DataBinding都是Android Jetpack的重要組成部分,這些東西能讓我們?cè)诎沧块_(kāi)發(fā)下實(shí)現(xiàn)數(shù)據(jù)和UI分離,使MainActivity等界面中的代碼更加簡(jiǎn)介清晰,DataBinding甚至能夠使用聲明性格式將布局中的界面組件綁定到應(yīng)用中的數(shù)據(jù)源。
初識(shí)viewBinding
viewBinding的作用就是訪問(wèn)xml中的控件,通常來(lái)說(shuō)訪問(wèn)xml中的控件方式有三種。
- 通過(guò)
findViewById<>(),這是最經(jīng)典的方法,但是如果想訪問(wèn)的控件數(shù)目較多則會(huì)造成代碼冗余,減緩開(kāi)發(fā)效率,不推薦使用。 - 在gradle中的plugins中添加
kotlin-android-extensions,通過(guò)拓展方法直接通過(guò)id來(lái)訪問(wèn),但是AS已經(jīng)標(biāo)注此方法已過(guò)時(shí),不推薦使用。 - 通過(guò)
viewBinding視圖綁定,也是可以通過(guò)id(實(shí)際上只是系統(tǒng)生成的屬性與id同名)來(lái)訪問(wèn),簡(jiǎn)單高效,推薦使用。
使用 viewBinding 之前要在 gradle -> android 中添加
android {
...
buildFeatures {
dataBinding true
}
}
開(kāi)啟此功能后,系統(tǒng)會(huì)為每個(gè) layout 都產(chǎn)生一個(gè)綁定類(lèi),如果有一個(gè)叫activity_main.xml 的布局文件,則會(huì)生成一個(gè) MainActivityBinding 的綁定類(lèi),我們只需要獲取此綁定類(lèi),并將其添加在setContentView中就可以用id來(lái)訪問(wèn)控件了。
class MainActivity : AppCompatActivity() {
//1.聲明一個(gè)變量用來(lái)接收綁定類(lèi)
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* 2.獲取binding類(lèi)對(duì)象
* layoutInflater: LayoutInflater 布局解析器
* 代碼中只能使用對(duì)應(yīng)的View或者ViewGroup無(wú)法使用xml文件
* 需要使用布局解析器來(lái)將xml文件轉(zhuǎn)化為對(duì)應(yīng)的View/ViewGroup
*/
binding = ActivityMainBinding.inflate(layoutInflater)
//3.將binding類(lèi)綁定的視圖和MainActivity關(guān)聯(lián)
setContentView(binding.root)
}
}
接下來(lái)就可以方便快捷的訪問(wèn)這些控件了,通過(guò) binding類(lèi)對(duì)象名.控件id來(lái)訪問(wèn)。
binding.button.setOnClickListener {
number++
binding.textView.text = "$number"
}
初始ViewModel
ViewModel是什么呢?究竟為什么要使用ViewModel呢?
架構(gòu)組件為界面控制器提供了
ViewModel輔助程序類(lèi),該類(lèi)負(fù)責(zé)為界面準(zhǔn)備數(shù)據(jù)。在配置更改期間會(huì)自動(dòng)保留ViewModel對(duì)象,以便它們存儲(chǔ)的數(shù)據(jù)立即可供下一個(gè) activity 或 fragment 實(shí)例使用。
在一些可能會(huì)造成activity銷(xiāo)毀或重啟的情況下(如屏幕旋轉(zhuǎn))會(huì)造成數(shù)據(jù)丟失,那我們又是怎么解決的呢?看看下面這張ViewModel生命周期圖吧。

我們發(fā)現(xiàn)不管是activity被創(chuàng)建還是旋轉(zhuǎn)還是結(jié)束銷(xiāo)毀,ViewModel都一直存在,能一直起到數(shù)據(jù)保存的功能,最典型的例子就是activity發(fā)生旋轉(zhuǎn)時(shí),會(huì)經(jīng)歷 onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume 一系列過(guò)程,簡(jiǎn)單來(lái)說(shuō)就是會(huì)重啟這個(gè)activity,如果不加額外操作勢(shì)必會(huì)引起數(shù)據(jù)丟失,因此我們需要一個(gè)很好的方法來(lái)避免此問(wèn)題。
ViewModel的優(yōu)點(diǎn)還很多,如共享數(shù)據(jù),UI和數(shù)據(jù)分離等等,現(xiàn)在來(lái)說(shuō)說(shuō)怎么使用ViewModel吧。上面提到屏幕旋轉(zhuǎn)數(shù)據(jù)丟失問(wèn)題,我們就舉這個(gè)例子來(lái)說(shuō)明ViewModel的優(yōu)點(diǎn),xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
這就是一個(gè)TextView顯示數(shù)字,和一個(gè)Button點(diǎn)擊一下,TextView中的數(shù)字加1,我們發(fā)現(xiàn)當(dāng)點(diǎn)擊Button使TextView顯示為5時(shí),旋轉(zhuǎn)屏幕后(前提是在Manifest中開(kāi)啟了此功能),這時(shí)會(huì)發(fā)現(xiàn)TextView中的數(shù)字又變成了零。
那接下里就用ViewModel來(lái)解決這個(gè)問(wèn)題。
首先創(chuàng)建一個(gè)MainViewModel繼承自ViewModel,在MainViewModel中完成數(shù)據(jù)的聲明以及各種邏輯。
class MainViewModel: ViewModel() {
var number = 0
fun addOne(){
number++
}
}
在MainActivity中訪問(wèn),如下所示:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
//這種獲得model對(duì)象的方式需要在gradel中加入依賴
//implementation("androidx.fragment:fragment-ktx:1.5.0")
private val model: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 這是另一種接收model的方式,前面那種類(lèi)似于單例模式
// model = ViewModelProvider(this)[MainViewModel::class.java]
if (binding.textView.text == "0"){
binding.textView.text = model.number.toString()
}
binding.button.setOnClickListener {
model.addOne()
binding.textView.text = model.number.toString()
}
}
}
這樣整好后一運(yùn)行,不管怎么旋轉(zhuǎn)屏幕,TextView中的數(shù)據(jù)都不會(huì)丟失,這樣就解決了這個(gè)問(wèn)題。
初識(shí)LiveData
LiveData一般和ViewModel聯(lián)合起來(lái)使用,LiveData是一種可觀察的數(shù)據(jù)存儲(chǔ)器類(lèi),當(dāng)?shù)讓訑?shù)據(jù)發(fā)生變化時(shí),LiveData 會(huì)通知 Observer 對(duì)象。您可以整合代碼以在這些 Observer 對(duì)象中更新界面。這樣一來(lái),您無(wú)需在每次應(yīng)用數(shù)據(jù)發(fā)生變化時(shí)更新界面,因?yàn)橛^察者會(huì)替您完成更新。
LiveData的使用步驟
1.在ViewModel中聲明需要監(jiān)聽(tīng)的對(duì)象
var number = MutableLiveData(0)
2.在ViewModel中實(shí)現(xiàn)數(shù)據(jù)改變邏輯
fun addOne(){
number.postValue(number.value!!+1)
}
3.界面中獲取ViewModel對(duì)象
val model: MainViewModel by viewModels()
4.觀察數(shù)據(jù)并實(shí)現(xiàn)改變之后需要處理的業(yè)務(wù)邏輯
model.number.observe(this){
binding.textView.text = "$it"
}
其中MainActivity是這樣的
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val model: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
model.addOne()
}
//觀察數(shù)據(jù)并實(shí)現(xiàn)改變之后需要處理的業(yè)務(wù)邏輯
model.number.observe(this){
binding.textView.text = "$it"
}
}
}
這樣也能解決屏幕因旋轉(zhuǎn)而數(shù)據(jù)丟失的問(wèn)題,顯然這種 ViewModel + LiveData 的方式比單獨(dú)的 ViewModel 的方式更簡(jiǎn)單,代碼更加簡(jiǎn)潔明了。
初識(shí)DataBinding
DataBinding的作用是實(shí)現(xiàn)了在xml文件中綁定數(shù)據(jù)和點(diǎn)擊事件等功能,從而不需要在MainActivity中編寫(xiě)這部分代碼,減輕了MainActivity界面中的代碼,使開(kāi)發(fā)者能夠更專注于核心代碼的編寫(xiě)。

此例的DataBinding是在ViewModel中實(shí)現(xiàn)的
數(shù)據(jù)綁定庫(kù)與 Android Gradle 插件捆綁在一起。無(wú)需聲明對(duì)此庫(kù)的依賴項(xiàng),但必須啟用它。啟用方式和 ViewBinding 一樣,需要在 gradle -> android 中添加如下代碼:
android {
...
buildFeatures {
dataBinding true
}
}
之后在 xml 中更改根標(biāo)簽,使用快捷鍵 ALT + ENTER 選中 convert to data binding layout。
1.在xml中給按鈕綁定事件:類(lèi)似于Lambda,注意->兩邊的空格
android:onClick="@{() -> model.addOne()}"
2.在xml中綁定數(shù)據(jù)
android:text="@{String.valueOf(model.number)}"
//如果本身number是字符串則只需這樣寫(xiě) ->
android:text="@{model.number}"
3.將Activity中創(chuàng)建的model對(duì)象設(shè)置給DataBinding中申明的變量
binding.model= model
binding.lifecycleOwner = this
這樣就大致完成了DataBinding的操作,能夠在xml中使用數(shù)據(jù)或者對(duì)界面完成交互。下面是完整的xml中的代碼:
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="自己定義的變量名" 如model
type="類(lèi)型的完整路徑" 如com.example.lineData.MainViewModel
>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> model.addOne()}"
android:text="@{String.valueOf(model.number)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>