一、ViewModel
ViewModel的一個重要作用就是幫助Activity分擔一部分的工作,專門存放關於界面相關的數據。只要界面上能看到的數據,都應該存放到ViewModel中。另外一個很重要的特性就是當手機屏幕發生旋轉時不會被重新創建,只有當acitivity被退出是跟着一起銷燬。
1.基本用法
一般每一個Activity和Fragment都會創建一個對應的ViewModel。
下面是一個簡單的計數器的例子:
1.導包:
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
2.xml界面 簡單的按鈕和textView
<?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/info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.519"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.123" />
<Button
android:id="@+id/button"
android:layout_width="403dp"
android:layout_height="52dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.52"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="187dp"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
3.創建MianViewModel
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
var counter = 0
}
4.MainActivity的使用
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
button.setOnClickListener {
viewModel.counter++
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
info_text.text = viewModel.counter.toString()
}
}
需要注意的點:
不可以在onCreate()中創建viewModel的實例,一定要使用ViewModelProvider來獲取。因爲ViewModel有其獨立的生命週期,在onCreate中直接創建,會每次都創建一個實例,如果屏幕發生旋轉就無法保留其自己的數據。
2.向ViewModel中傳遞參數
向ViewModel中傳遞參數,我們需要藉助ViewModelProvider.Factory。
1.首先修改ViewModel,構造函數中添加一個參數
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = countReserved
}
2.創建一個MainViewModelFactory
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countReserved) as T
}
}
3.修改MianActivity中的代碼
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp: SharedPreferences//使用SharedPreference用於恢復數據
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved", 0)
viewModel = ViewModelProvider(
this,
MainViewModelFactory(countReserved)
).get(MainViewModel::class.java)
btn_add_count.setOnClickListener {
viewModel.counter++
refreshCounter()
}
btn_clear.setOnClickListener {
viewModel.counter = 0
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
tv_info.text = viewModel.counter.toString()
}
override fun onPause() {
super.onPause()
sp.edit {//將數據存儲到SharedPreference
putInt("count_reserved", viewModel.counter)
}
}
}
二、Lifecycles
Lifecycles是爲了讓任何一個類輕鬆感知Activity的生命週期。使用方法也是非常簡單。
1.首先實現LifecycleObserver的接口
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onActivityStart(){
Log.d("MyObserver","activity start")
}
}
2.如果Activity繼承自AppCompatActivity
添加:lifecycle.addObserver(MyObserver())
如果想要獲取當前Activity的生命週期狀態,則在MyObserver的構造函數中將Lifecycle對象傳進來即可
class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver {
...
lifecycle.currentState//即可獲取當前生命週期狀態
}
三、LiveData
1.LiveData通常是和ViewModel一起使用。LiveData可以包含任意類型的數據,並在數據發生變化時通知給觀察者。下面我們將上面的計數器使用LiveData對數據進行包裝,然後在Activity中觀察。
1.修改MainViewModel
class MainViewModel(countReserved: Int) : ViewModel() {
private val _counter = MutableLiveData<Int>()
val counter: LiveData<Int>
get() = _counter
init {
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
}
2.修改MainActivity中的代碼
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(MyObserver(lifecycle))
setContentView(R.layout.activity_main)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved", 0)
viewModel = ViewModelProvider(
this,
MainViewModelFactory(countReserved)
).get(MainViewModel::class.java)
btn_add_count.setOnClickListener {
viewModel.plusOne()
}
btn_clear.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer { count ->
tv_info.text = count.toString()
})
}
需要注意的是:
使用LiveData設置數據時有setValue和postValue兩種方法,如果是在子線程中設置數據則需要調用postValue,否則將報錯。
上面的例子,將_counter設置爲私有類型是爲了不將LiveData中的數據暴露給外部,防止外部對數據進行修改。推薦的做法是,永遠暴漏不可變的部分給ViewModel外部,只能觀察數據的變化,設置LiveData的操作只能在ViewModel內部完成。
2.map和switchmap
(1)map
map()方法是將實際包含數據的LiveData和僅用於觀察的LiveData進行轉換。
例如:我們有一個User的類:
data class User(var firstName: String, var lastName: String, var age: Int)
但是在使用時我們僅需要獲取名字,完全不關心用戶的年齡,這是我們就不需要將整個User類全部暴漏到外部,下面我們修改一下ViewModel中的代碼:
class MainViewModel(countReserved: Int) : ViewModel() {
private val userLiveData = MutableLiveData<User>()
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
...
}
(2)swithmap
switchmap的應用場景比較單一,如果ViewModel中某個LiveData對象是調用另外的方法獲取的,那麼我們需要藉助switchmap方法,將這個LiveData的對象轉換爲可觀察的LiveData對象。
1.創建一個單例類
object Repository {
fun getUser(userId: String): LiveData<User> {
var liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 0)
return liveData
}
}
2.在ViewModel中添加代碼
fun getUser(userId: String): LiveData<User> {
return Repository.getUser(userId)
}
但是此時需要注意的是,我們每次調用getUser()時都會獲取一個新的實例,根本無法觀察的數據的變化,此時我們需要使用switchmap將這個LiveData的對象轉換成爲一個可觀察的LiveData對象:
private val userIdLiveData = MutableLiveData<String>()
val user: LiveData<User> = Transformations.switchMap(userIdLiveData) { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String) {
userIdLiveData.value = userId
}