前提:導入所有依賴,開啟DataBinding
app的build.gradle
android {
defaultConfig {
。。。
dataBinding {
enabled true
}
}
}
dependencies {
def lifecycle_version = "2.1.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
// alternatively - just LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
// AndroidX libraries use this lightweight import for Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}
ViewModel
1.定義Model類集成androidx.lifecycle.ViewModel,並編寫數據操作邏輯
class MyViewModel : ViewModel() {
private var mNumber = MutableLiveData<Int>(0)
fun getNumber() = mNumber
fun setNumber(number: Int) {
mNumber.value = number
}
}
// 使用MutableLiveData類包裝數據,支持livedata
//為了在ViewModel中使用Context,可以繼承AndroidViewModel
2.Activity類中直接調用Model類的方法來操作數據
ViewModel的另一個用途:在Fragment之間共享數據
以下示例了來自jetpack官方文檔:
/*
在 Fragment 之間共享數據
Activity 中的兩個或更多 Fragment 需要相互通信是一種很常見的情況。想象一下主從 Fragment 的常見情況,假設您有一個 Fragment,在該 Fragment 中,用戶從列表中選擇一項,還有另一個 Fragment,用於顯示選定項的內容。這種情況不太容易處理,因為這兩個 Fragment 都需要定義某種接口描述,並且所有者 Activity 必須將兩者綁定在一起。此外,這兩個 Fragment 都必須處理另一個 Fragment 尚未創建或不可見的情況。
可以使用 ViewModel 對象解決這一常見的難點。這兩個 Fragment 可以使用其 Activity 范圍共享 ViewModel 來處理此類通信,如以下示例代碼所示:
*/
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
/*
請注意,這兩個 Fragment 都會檢索包含它們的 Activity。這樣,當這兩個 Fragment 各自獲取 ViewModelProvider 時,它們會收到相同的 SharedViewModel 實例(其范圍限定為該 Activity)。
此方法具有以下優勢:
1)Activity 不需要執行任何操作,也不需要對此通信有任何了解。
2)除了 SharedViewModel 約定之外,Fragment 不需要相互了解。如果其中一個 Fragment 消失,另一個 Fragment 將繼續照常工作。
3)每個 Fragment 都有自己的生命周期,而不受另一個 Fragment 的生命周期的影響。如果一個 Fragment 替換另一個 Fragment,界面將繼續工作而沒有任何問題。
*/
LiveData
1.注冊觀察器
mViewModel.getNumber().observe(
this,
Observer { it->
mText1.text = it.toString()
mText2.text = it.toString()
}
)
// Model類中必須使用包裝器才可以進行監聽
// it參數類型為int,即原本數據類型而非包裝器
DataBinding
1.將Activity與xml進行連接
onCreate中,使用
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) as ActivityMainBinding
代替
setContentView(R.layout.activity_main)
2.xml文件中插入數據類
使用
<layout>
<data>
<variable //該變量在本xml文件中使用
name="data" //變量名
type="com.example.test.MyViewModel" /> //變量類型
</data>
<ConstraintLayout>
。。。
UI
。。。
<TextView
android:text="@{String.valueOf(data.mNumber)}">
<Button
android:onClick="@{()->data.setNumber(data.mNumber + 1)}">
<ConstraintLayout>
</layout>
3.將數據綁定至xml中
binding.data = mViewModel //綁定數據
binding.lifecycleOwner = this //綁定生命周期
改變配置、內存回收等情況下的數據保持
1.使用saveInstanceState
在onSaveInstanceState(Bundle)和onRestoreInstanceState(Bundle)中進行操作
2.使用SharePreference
3.使用ViewModel持有一個SavedStateHandle對象,利用該對象保存數據
class MyViewModel(var handle : SavedStateHandle) : ViewModel() {
init {
if (!handle.contains("NUMBER"))
handle.set("NUMBER", 0)
}
fun getNumber() : LiveData<Int> = handle.getLiveData("NUMBER")
fun add() {
val number : Int = handle.get("NUMBER")!!
handle.set("NUMBER", number + 1)
}
}
經過改造后的Activity
//Activity
package com.example.test
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
import com.example.test.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val mViewModel = ViewModelProviders.of(this)[MyViewModel::class.java]
/*
最新版本用以下方法創建ViewModel:
val mViewModel = ViewModelProvider(this).get(MyViewModel::class)
*/
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) as ActivityMainBinding
binding.data = mViewModel
binding.lifecycleOwner = this
//使用ViewModel操作數據
}
}
//Model類
package com.example.test
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
private var mNumber = MutableLiveData<Int>(0)
fun getNumber() = mNumber
fun setNumber(number: Int) {
mNumber.value = number
}
}
//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="data"
type="com.example.test.MyViewModel" />
</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:layout_marginTop="80dp"
android:text="@{String.valueOf(data.number)}"
android:freezesText="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="+1"
android:onClick="@{()->data.setNumber(data.number + 1)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="+2"
android:onClick="@{()->data.setNumber(data.number + 2)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button1" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="@{String.valueOf(data.number)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>