【Jetpack】LiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

时间:2022-11-15 00:58:24


文章目录

  • 一、LiveData 简介
  • 二、LiveData 使用方法
  • 三、ViewModel + LiveData 简单示例
  • 1、ViewModel + LiveData 代码
  • 2、Activity 组件代码
  • 3、运行效果展示
  • 四、ViewModel + LiveData + Fragment 通信示例
  • 1、ViewModel + LiveData 代码
  • 2、Activity 组件代码
  • Activity 代码
  • 布局文件
  • 3、Fragment 代码
  • 第一个 Fragment 代码
  • 第一个 Fragment 布局文件
  • 第二个 Fragment 代码
  • 第二个 Fragment 布局文件
  • 4、运行效果展示



一、LiveData 简介



在 视图 View 与 数据模型 Model 通过 ViewModel 架构组件 进行绑定后 , 可以立即 将 ViewModel 中的数据设置到 UI 界面中 ,

运行过程中 , 在 UI 界面中 , 可以 修改 ViewModel 中的值 , 并 将新的值设置在 视图 View 中 ;

但是 , 如果 数据是在 ViewModel 中发生的改变 , 那么如何 通知 UI 来进行 视图 View 的更新 操作呢 ?

这里引入 LiveData 架构组件 , 在 ViewModel 中 , 可以 通过 LiveData 将数据修改的信息发送给 视图 View , 通知 UI 界面进行修改 ;

【Jetpack】LiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

场景举例 : 在 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 显示 ;

执行时切换屏幕方向 , 不影响数据累加显示 ;

【Jetpack】LiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )



四、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 versinotallow="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:cnotallow=".MainActivity">

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientatinotallow="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 versinotallow="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:cnotallow=".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 versinotallow="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:cnotallow=".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 中显示 , 并且横竖屏切换时 , 数据没有丢失 ;

【Jetpack】LiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )