一、LiveData 简介
在 视图 View 与 数据模型 Model 通过 ViewModel 架构组件 进行绑定后 , 可以立即 将 ViewModel 中的数据设置到 UI 界面中 ,
运行过程中 , 在 UI 界面中 , 可以 修改 ViewModel 中的值 , 并 将新的值设置在 视图 View 中 ;
但是 , 如果 数据是在 ViewModel 中发生的改变 , 那么如何 通知 UI 来进行 视图 View 的更新 操作呢 ?
这里引入 LiveData 架构组件 , 在 ViewModel 中 , 可以 通过 LiveData 将数据修改的信息发送给 视图 View , 通知 UI 界面进行修改 ;
场景举例 : 在 ViewModel 中申请 HTTP 服务器数据 , 请求发送后 , 不知道什么时候才能获得响应 , 如果 过一段时间服务器才反馈响应数据 , 此时只能 通过 LiveData 将 ViewModel 的数据修改通知给 视图 View ;
二、LiveData 使用方法
首先 , 在 ViewModel 视图模型 中定义 LiveData 数据 , 如 MutableLiveData<T>
,
class MyViewModel: ViewModel {
var second: MutableLiveData<Int> = MutableLiveData<Int>()
constructor() {
second.value = 0
}
}
在该类中提供了 postValue
和 setValue
两个函数 ,
- 在 UI 主线程 中调用
setValue
函数 , - 在 非 UI 线程的子线程 中调用
postValue
函数 更新数据 ;
public class MutableLiveData<T> extends LiveData<T> {
@Override
public void postValue(T value)
@Override
public void setValue(T value)
}
然后 , 在 Activity 组件中 , 调用 LiveData#observe
函数 , 添加数据变化监听器 androidx.lifecycle.Observer<T>
, 一旦 LiveData 数据发生了改变 , 就会 回调 Observer 监听器中的 onChanged
函数 ;
// 设置 LiveData 监听
myViewModel.second.observe(this, object : androidx.lifecycle.Observer<Int> {
override fun onChanged(t: Int?) {
// 将 ViewModel 中的数据设置到 视图 View 组件中
textView.setText("${myViewModel.second.value}")
}
})
三、ViewModel + LiveData 简单示例
设置一个定时器 , 定时更新数据 , 在 ViewModel 中数据发生了改变 , 需要 主动通知 视图 View 进行修改 ;
使用 传统的开发方式 , 可以使用 线程通信 , Handler 或者 广播 等形式 , 在子线程中通知主线程更新 UI ;
使用 LiveData 后 , 将数据定义在 LiveData 中 , 然后在 Activity 中 为 LiveData 添加 Observer 监听器 , 当 LiveData 数据发生改变时 , 会自动回调该监听器的 onChange 方法 ;
1、ViewModel + LiveData 代码
自定义 ViewModel 子类继承 ViewModel , 在 ViewModel 中 , 定义 LiveData 类型的数据 , 此处选择使用 MutableLiveData<Int> 数据类型 , 维护一个 Int 类型的数据 , 当该 Int 值发生改变时 , 会触发 LiveData 设置的 Observer 监听器 ;
package kim.hsl.livedatademo
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel: ViewModel {
var second: MutableLiveData<Int> = MutableLiveData<Int>()
constructor() {
second.value = 0
}
}
2、Activity 组件代码
在 Activity 系统组件中 , 绑定 ViewModel , 从 ViewModel 中获取 LiveData 显示到 UI 界面中 , 并为该 LiveData 设置 Observer 监听器 , 监听 LiveData 的数据变化 ;
启动 Timer 定时器 , 修改 ViewModel 中的 LiveData 数据 , 在 LiveData 数据发生改变时 , 会自动回调 Observer 监听器的 onChanged 函数 ;
package kim.hsl.livedatademo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import java.util.*
class MainActivity : AppCompatActivity() {
lateinit var textView: TextView
lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取 视图 View 组件
textView = findViewById(R.id.textView)
// 获取 ViewModel
myViewModel = ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory(application))
.get(MyViewModel::class.java)
// 将 ViewModel 中的数据设置到 视图 View 组件中
textView.setText("${myViewModel.second.value}")
// 设置 LiveData 监听
myViewModel.second.observe(this, object : androidx.lifecycle.Observer<Int> {
override fun onChanged(t: Int?) {
// 将 ViewModel 中的数据设置到 视图 View 组件中
textView.setText("${myViewModel.second.value}")
}
})
// 启动定时器, 将 ViewModel 中的数据自增
startTimer()
}
fun startTimer() {
Timer().schedule(object : TimerTask(){
override fun run() {
// 获取 ViewModel 中的数据
var second: Int? = myViewModel.second.value
// 将 ViewModel 中的数据自增 1
myViewModel.second.postValue(second?.plus(1))
}
}, 1000, 1000)
}
}
3、运行效果展示
应用启动后 , 在界面中启动定时器 , 对 ViewModel 中的 LiveData 数据进行累加 , LiveData 设置了 Observer 监听 , 数据改变时回调 Observer#onChanged 函数更新 UI 显示 ;
执行时切换屏幕方向 , 不影响数据累加显示 ;
[图片上传失败...(image-22711d-1683294505341)]
四、ViewModel + LiveData + Fragment 通信示例
在 Activity 系统组件中 设置两个 Fragment , 两个 Fragment 之间通过 ViewModel + LiveData 进行通信 ;
在其中一个 Fragment 中设置 SeekBar 拖动条 , 将数值设置到另外一个 Fragment 中的 TextView 中显示 ;
1、ViewModel + LiveData 代码
自定义 ViewModel 子类继承 ViewModel , 在 ViewModel 中 , 定义 LiveData 类型的数据 , 此处选择使用 MutableLiveData<Int> 数据类型 , 维护一个 Int 类型的数据 , 当该 Int 值发生改变时 , 会触发 LiveData 设置的 Observer 监听器 ;
package kim.hsl.livedatademo
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel: ViewModel {
var progress: MutableLiveData<Int> = MutableLiveData<Int>()
constructor() {
progress.value = 0
}
}
2、Activity 组件代码
在该 Activity 组件中 , 维护了两个 Fragment , 两个 Fragment 之间借助 ViewModel + LiveData 进行通信 ;
Activity 代码
package kim.hsl.livedatademo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
布局文件
在 Activity 中设置了两个 Fragment , 它们之间借助 ViewModel + LiveData 进行通信 ;
<?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">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView1"
android:name="kim.hsl.livedatademo.Fragment1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView2"
android:name="kim.hsl.livedatademo.Fragment2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
3、Fragment 代码
在该 Activity 组件中 , 维护了两个 Fragment , 两个 Fragment 之间借助 ViewModel + LiveData 进行通信 ;
第一个 Fragment 代码
先将 ViewModel 中的 LiveData 数据中的 进度值设置给 SeekBar ,
目的是为了在屏幕旋转时 , 可随时恢复数据 ;
在 SeekBar 的拖动数据中 , 修改 ViewModel 中的 LiveData 数据 ,
当数据修改时 , 对应的 Fragment2 中的 TextView 会刷新显示新的数据 ;
package kim.hsl.livedatademo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
class Fragment1: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 设置布局
val root: View = inflater.inflate(R.layout.fragment1, container, false)
// 获取拖动条
var seekBar: SeekBar = root.findViewById(R.id.seekBar)
// 获取 ViewModel
var viewModel: MyViewModel = ViewModelProvider(requireActivity(),
ViewModelProvider.AndroidViewModelFactory(requireActivity().application))
.get(MyViewModel::class.java)
seekBar.progress = viewModel.progress.value!!
// 设置进度条拖动事件
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
viewModel.progress.value = progress
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
})
return root
}
}
第一个 Fragment 布局文件
Fragment1 中维护了一个 SeekBar 拖动条组件 ;
<?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">
<SeekBar
android:id="@+id/seekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:max="100"
android:min="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
第二个 Fragment 代码
在 Fragment2 中 , 只放了一个 TextView 组件 , 该组件显示的是 ViewModel 中的 LiveData 数据 , 当该 LiveData 数据发生改变时 , 对应 TextView 显示也随之更新 ;
package kim.hsl.livedatademo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
class Fragment2: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 设置布局
val root: View = inflater.inflate(R.layout.fragment2, container, false)
// 获取文本组件
val textView: TextView = root.findViewById(R.id.textView)
// 获取 ViewModel
var viewModel: MyViewModel = ViewModelProvider(requireActivity(),
ViewModelProvider.AndroidViewModelFactory(requireActivity().application))
.get(MyViewModel::class.java)
// 设置文本显示内容
textView.setText("${viewModel.progress.value}")
// 设置 LiveData 监听
viewModel.progress.observe(requireActivity(), object : androidx.lifecycle.Observer<Int> {
override fun onChanged(t: Int) {
textView.setText("${viewModel.progress.value}")
}
})
return root
}
}
第二个 Fragment 布局文件
Fragment2 中维护了 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/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="50sp"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
4、运行效果展示
拖动 Fragment1 中的进度条 , 将进度条的进度 在 Fragment2 中的 TextView 中显示 , 并且横竖屏切换时 , 数据没有丢失 ;