前言
时间: 23/09/17
AndroidStudio
版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin
Gradle版本: 8.0 Gradle plugin Version: 8.1.1
基本使用
-
添加依赖
implementation("androidx.lifecycle:lifecycle-livedata:2.6.2") implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
-
修改MainViewModel
-
给ViewModel添加 MutableLiveData<> 类型变量。
private var result = MutableLiveData<Long>()
-
设置LiveData类型常量并赋值为result
val _result: LiveData<Long> = result
-
因为result是私有变量,所以需要写一个修改值的方法
fun setResult(showLong: Long) { result.value = showLong time = showLong.times(1000) }
-
整体
class MainViewModel : ViewModel() { var time: Long = 5000 private var result = MutableLiveData<Long>() val _result: LiveData<Long> = result fun setResult(showLong: Long) { result.value = showLong time = showLong.times(1000) } }
-
-
添加一个MainManage类,用来控制ViewModel中的数据变化
class MainManager(mainViewModel: MainViewModel) : LifecycleEventObserver { private val TAG = "MainManager" private var countDownTimer: CountDownTimer? = object : CountDownTimer(mainViewModel.time, 1000) {//添加一个倒计时,时间为time=5000ms override fun onTick(timeAfter: Long) { Log.d(TAG, "倒计时 --- ${timeAfter / 1000}s") mainViewModel.setResult(timeAfter / 1000) } override fun onFinish() { Log.d(TAG, "倒计时 结束") } } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { Log.d(TAG, "onStateChanged: $event") when (event) { Lifecycle.Event.ON_START -> countDownTimer?.start() Lifecycle.Event.ON_DESTROY -> { countDownTimer?.cancel() countDownTimer = null } else -> {} } } }
-
MainActivity中添加LiveData的observe
class MainActivity : AppCompatActivity() { private val TAG = "MainActivity" private lateinit var mainViewModel : MainViewModel private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] val mainManager = MainManager(mainViewModel) lifecycle.addObserver(mainManager) mainViewModel._result.observe(this) {//观察_result,_result的值会随着result变化而变化 binding.showTime.text = "倒计时还剩下 ${it}s"//it是LiveData<>规定的类型Long if (it == 0L) { Log.d(TAG, " OVER!! ") binding.showTime.text = "倒计时结束"//这里使用了ViewBinding,这个后续讲。 //不会使用可以先用findViewById绑定好视图再操作 //其实前面两篇文章我已经用到了这个,之前写代码的习惯,这个也是jetpack的组件技术,所以就后续再细说 } } } }
Log显示
这里用的是私有变量,如果使用的kotlin默认的public的话,可以直接使用变量去完成这个observe的操作。但是共用变量可能会导致数据操作的完整性出现问题,因此我这边直接用的私有来声明
LiveData<>类型转化方法 - map & switchMap
从前面所讲的和代码来看,我们观察所接收到的是LiveData所指定的类型,如果这个类型是我们自己写的一个实体(entity)类,那么我们的需求有很大的可能是要拿到这个类中的某个属性,于是我们在拿到这个实体类后还需要进行加工,这样我们每次都要使用getter方法增加 Activity 中的代码量。
这个时候就需要使用到类型转化方法 —— map 或者 switchMap 。
map
编写一个Data类Team
,设置三个属性,id、name、point
data class Team(var name: String, var id: Long, var point: Int)
然后再编写一个TeamViewModel,对应Team。
class TeamViewModel : ViewModel() {
private var teamData = MutableLiveData<Team>()
val _teamData: LiveData<Team> = teamData
fun setTeamData(team: Team) {
teamData.value = team
}
}
然后在Activity中observe,这个时候观察的对象就是Team。如果我们只是想要显示point属性,那么就需要使用getter。
假设我们的需求就只是获取每一个team的分数,那我们可以对ViewModel中的LiveData进行修改。
由于书本中Transformations这个类被Google “优化” 了。现在可以直接用LiveData使用map方法,即teamData.map
。
val _teamData: LiveData<Int> = teamData.map {
it.point
}
这样就可以把LiveData指定的类型改为point的类型。在观察时直接使用即可。
布局和Activity的代码往下看
switchMap
switchMap转化是将从外部,例如其它app进程或网络中获取到的LiveData转化为可观察的方式。举个栗子。
新建一个TeamRespository类,添加getTeamPoint方法。
class TeamRespository {
/*
* 根据id获取得分,’模拟‘网络请求
*/
fun getTeamPoint(id: Int): LiveData<Int> {
val teamScoreLiveData = MutableLiveData<Int>()
if (id == 7) {//图个方便
teamScoreLiveData.value = 55
} else {
teamScoreLiveData.value = 0
}
return teamScoreLiveData
}
}
这个getTeamPoint 方法后续会在 ViewModel 中调用 。先看下layout布局。
新建一个activity_team.xml到layout中。
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<TextView
android:id="@+id/team_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="32sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/team_info">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="number"
android:hint="Please text Team ID" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="submit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
<TextView
android:id="@+id/score_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submit" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
主要是添加了一个输入框和按钮,来通过输入的ID获取得分。
看到按钮,这个时候第一时间想到的实现方法,就是在点击监听中,拿到ID,调取获取得分的方法,显示出结果。
在ViewModel中添加一个返回类型为LiveData的getPoint方法,这样才可以在 Activity 中添加 observe 方法。
//viewmodel中
fun getPoint(id: Int): LiveData<Int> {
return TeamRespository().getTeamPoint(id)
}
//activity中
teamViewModel.setTeamData(team1)
binding.submit.setOnClickListener {
teamViewModel.getPoint(binding.inputText.text.toString().toInt()).observe(this, Observer {
binding.scoreShow.text = "score: $it"
})
}
看着会觉得有点奇怪,因为每次获取得分的时候都会返回一个新的LiveData,所以这个observe其实压根没啥作用,甚至不如直接写成
binding.submit.setOnClickListener {
binding.scoreShow.text
= "score: ${teamViewModel.getPoint(binding.inputText.text.toString().toInt())}"
}
嘿嘿,甚至看着还更简单点。但是这中方式完全就和LiveData不搭边,甚至之前写的全都是冗余代码。
怎么解决这种情况呢?可以看到,我们是通过ID获取对应的分数的,那我们就可以定义一个可观察的id变量
//viewmodel中
private var teamIdLiveData = MutableLiveData<Int>()
fun setTeamId(teamId: Int) {
teamIdLiveData.value = teamId
}
然后通过switchMap将 teamIdLiveData 映射为返回的分数结果类型
//viewmodel中
var newPoint: LiveData<Int> = teamIdLiveData.switchMap {id: Int ->
return@switchMap TeamRespository().getTeamPoint(id)
}
这样我们就可以在activity中添加对newPoint的observe,点击事件则是调用setTeamId方法,因为调用了setTeamId方法 teamIdLiveData 的值才会改变,newPoint 的observe方法才会监听到变化执行相关操作。
teamViewModel.newPoint.observe(this) {
binding.scoreShow.text = "score: $it"
}
binding.submit.setOnClickListener {
teamViewModel.setTeamId(binding.inputText.text.toString().toInt())
}
result显示
总结
LiveData 一般都定义在 ViewModel 中,作为可观察数据,它具有生命周期感知的能力,可以确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
Lifecycle、ViewModel 和 LiveData之间的关系比较密切,一般都是结合三者一起使用。利用好这三者可以大幅度简化 Activity 中的代码,为后续 mvvm 框架的解耦作好铺垫。
本文没有对于 LiveData 的原理进行剖析,因为原理代码相对比较简单。感兴趣的可以自行阅读,主要方向是 observe 方法。