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

一、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
    }
}

在该类中提供了 postValuesetValue 两个函数 ,

  • 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 显示 ;

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

请添加图片描述

四、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 中显示 , 并且横竖屏切换时 , 数据没有丢失 ;

请添加图片描述


博客代码 :

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>