文章目录

  • ViewModel简介
  • ViewModel 的基本用法
  • 向ViewModel传递参数

ViewModel简介

ViewModel 应该算是Jetpack 中最重要的组件之一了。其实Android 平台上之所以会出现注入MVP、MVVM 之类的项目架构,就是因为在传统的开发模式下,Activity 的任务实在是太重了,既要负责逻辑处理,又要控制UI 提示,甚至还得处理网络回调,等等。在一个小项目中这样写或许没有什么问题,但是如果在大型项目中仍然使用这样写法的话,那么这个项目将会变得非常臃肿并且难以维护,因为没有任何架构上的划分。

ViewModel 的一个重要作用就是可以帮助Activity 分担一部分工作,它是专门用于存放于界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity 中,这样可以在一定程度上减少Activity 中的逻辑。

另外,ViewModel 还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候,Activity 会被重新创建,同时存放在Activity 中的数据也会丢失。而ViewModel的生命周期和Activity 不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity 退出的时候才会跟着Activity 一起销毁。因此,将与界面相关的变量存放在ViewModel 当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。ViewModel 的生命周期如图所示:

ViewModel 的基本用法

由于Jetpack 中的组件通常是以 AndroidX 库的形式发布的,因此一些通常的Jetpack 组件会在创建AndroidX 项目时自动被包含进去。不过如果我们想要使用 ViewModel 组件,还需要在app/build.gradle 文件中添加如下依赖:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

通常来讲,比较好的编程规范是给每一个 Activity 和 Fragment 都创建一个对应的ViewModel ,因此这里我们就位 MainActivity 创建一个对应的 MainViewModel 类,并让它继承自ViewModel ,代码如下所示:

import androidx.lifecycle.ViewModelclass MainViewModel: ViewModel() {}

根据前面所学的知识,所有与界面相关的数据都应该放在ViewModel 中。那么这里我们要实现一个计数器的功能,就可以在 ViewModel 中加入一个 counter 变量用于计数,如下所示:

class MainViewModel : ViewModel() {var counter: Int = 0
}

现在我们需要在界面上添加一个按钮,每点击一次按钮就让计数器加1,并且把最新的计数显示在界面上。修改activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><TextViewandroid:id="@+id/infoText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:textSize="32sp" /><Buttonandroid:id="@+id/plusOneBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="Plus One" /></LinearLayout>

布局文件非常简单,一个TextView 用于显示当前的计数,一个Button 用于对计数器加1。

接着我们开始实现计数器的逻辑,修改MainActivity 中的代码,如下所示:

class MainActivity : AppCompatActivity() {private lateinit var viewModel: MainViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)viewModel = ViewModelProvider(this).get(MainViewModel::class.java)plusOneBtn.setOnClickListener{viewModel.counter++refreshCounter()}refreshCounter()}private fun refreshCounter(){infoText.text = viewModel.counter.toString()}
}

先看下运行结果

注意:我们绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider 来获取 ViewModel 的实例,具体语法规则如下:

ViewModelProvider(<你的Activity 或 Fragment 实例>).get(<你的ViewModel>::class.java)

之所以这么写,是因为ViewModel 有其独立的生命周期,并且其生命周期要长于Activity 。如果我们在onCreate() 方法中创建一个ViewModel 的实例,那么每次onCreate() 方法执行的时候,ViewModel 都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了

除此之外的其他代码应该都是非常好理解的,我们提供了一个refreshCounter() 方法用来显示当前的计数,然后每次点击按钮的时候对计数器加1,并调用refreshCounter() 方法刷新计数

如果你尝试通过侧边工具栏旋转一下模拟器的屏幕,就会发现Activity 虽然被重新创建了,但是计数器的数据却没有消失

向ViewModel传递参数

上一小节中创建的 MainViewModel 的构造函数中没有任何参数,但是思考一下,如果我们确实需要通过构造函数来传递一些参数,应该怎么办呢?由于所有ViewModel 的实例都是通过ViewModelProvider 来获取的,因此我们没有任何地方可以向ViewModel 的构造函数中传递参数

当然,这个问题也不难解决,只需要借助ViewModelProvider.Factory 就可以实现了

现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了。接下来我们就对这一功能进行升级,保证即使在退出程序后又重新打开的情况下,数据仍然不会丢失。

相信你已经猜到了,实现这个功能需要在退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,传递给MainViewModel 。因此,这里修改 MainViewModel 中的代码,如下所示:

class MainViewModel(countReserved: Int) : ViewModel() {var counter: Int = countReserved
}

现在我们给 MainViewModel 的构造函数添加了一个 countReserved 参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给 counter 变量

前面已经说了需要借助 ViewModelProvider.Factory ,因此新建一个 MainViewModelFactory 类,并让它实现 ViewModelProviders.Factory 接口,代码如下所示:

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {return MainViewModel(countReserved) as T}
}

可以看到,MainViewModelFactory 的构造函数中也接收了一个 countReserved 参数。另外 ViewModelProvider.Factory 接口要求我们必须实现create() 方法,因此这里在create() 方法中我们创建了 MainViewModel 实例,并将 countReserved 参数传了进去。为什么这里就可以创建 MainViewModel 的实例了呢?因为create() 方法的执行时机和 Activity 的生命周期无关,所以不会产生之前提到的问题

另外,我们还得在界面上添加一个清零按钮,方便用户手动将计数器清零。修改activity_main.xml 中的代码,(在原来基础上增加一个按钮即可)如下所示:

<Buttonandroid:id="@+id/clearBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="Clear"/>

修改 MainActivity 中的代码

class MainActivity : AppCompatActivity() {private lateinit var viewModel: MainViewModelprivate lateinit var sp:SharedPreferencesoverride 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)plusOneBtn.setOnClickListener{viewModel.counter++refreshCounter()}clearBtn.setOnClickListener {viewModel.counter = 0refreshCounter()}refreshCounter()}private fun refreshCounter(){infoText.text = viewModel.counter.toString()}override fun onPause() {super.onPause()sp.edit{putInt("count_reserved",viewModel.counter)}}
}

现在重新运行程序,点击数次”Plus One“ 按钮,然后退出程序并重新运行,你会发现,计数器的值是不会丢失的,只有点击”Clear“ 按钮,计数器的值才会被清零。如图所示:

本章内容源自 郭霖大神的《第一行代码 第三版》

ViewModel 的基本用法相关推荐

  1. MVVM+ViewBinding+Kotlin入门

    本篇博客不作原理性分析,仅对还没有接触过MVVM,viewbinding,甚至说是kotlin的同学一个入门练习项目,通过此篇博客,可以对MVVM架构,以及最近比较火的ViewBinding有一个初步 ...

  2. Jetpack Compose 中的架构思想

    Jetpack Compose 中的架构总览 如果应用打算使用 Jetpack Compose 来开发,那么就可以跟以前的MVC.MVP.MVVM等乱七八糟的架构全部说拜拜,这些名词也将在Androi ...

  3. Dorado用法与示例

    Dorado用法与示例 dorado用后总结 一.dorado概念 dorado的产品全名是"dorado展现中间件".从产品形态上dorado由两部分组成,第一部分是一个具有AJ ...

  4. AutoMapper用法

    AutoMapper是对象到对象的映射工具.在完成映射规则之后,AutoMapper可以将源对象转换为目标对象. 作者:齐飞 原文:http://www.qeefee.com/article/auto ...

  5. 架构组件专栏 | ViewModel深入浅出

    本文是架构组件专栏的开篇文章,因此在文章开头我打算花些笔墨谈谈什么是架构组件以及我为什么打算写这个专栏. 谷歌官方为了帮助开发者加速开发并构建高质量的应用,推出了Jetpack.正如上图你所看到的,J ...

  6. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  7. html表格标签高级应用,asp.net core标签助手的高级用法TagHelper+Form

    上一篇博客我讲解了TagHelper的基本用法和自定义标签的生成,那么我就趁热打铁,和大家分享一下TagHelper的高级用法~~,大家也可以在我的博客下随意留言. 对于初步接触asp.net cor ...

  8. React简介及基础用法

    1.React简介 1.1.React概述 React 是用于构建用户界面的 javascript 库,具有声明式.组件化等特点. 1.2.MVC和MVVM模式 MVC(Model-View-Cont ...

  9. Model和ViewModel之间的通用MVVM数据交换

    目录 介绍 代码 示例用法 介绍 我一直在开发一个工具来帮助团队中的其他开发人员为他们的MVC应用程序创建菜单项.所讨论的工具是一个使用MVVM模式的WPF桌面应用程序.我正在编写模型和相关的视图模型 ...

最新文章

  1. 软考已报名可以更改科目吗
  2. SQL判断是否“存在“,还在用 count 操作?
  3. 推荐几个实用的Python“小伎俩”
  4. nginx反向代理vue访问时浏览器加载失败,出现 ERR_CONTENT_LENGTH_MISMATCH 问题
  5. 通过零拷贝实现高效的数据传输(操作系统)
  6. aggregations 详解1(概述)
  7. 马化腾又要发红包!648亿港元,最多腾讯2%股份,我酸了...
  8. python笔记:datetime模块中的函数
  9. springCloud Alibaba 与 nacos
  10. ubantu下谷歌浏览器安装包
  11. 战地一自定义服务器怎么搜索,战地1怎么快速加入服务器?多种加入方法一览...
  12. 主流数据库对比,主流数据库性能、选型对比
  13. 同步发电机励磁调节实验原理_【每日一学】同步发电机的运动方程
  14. gpt和mbr用来装服务器系统,给win10分区用MBR还是GPT?详解MBR还是GPT的区别
  15. phpstudy安装php8.0和php8.1的方法(内含VC运行库)
  16. spark解决Illegal pattern component: XXX NoSuchFieldError: KRYO_SARG_BUFFER
  17. Java基础之匿名内部类,匿名内部类是什么?为什么要用匿名内部类,匿名内部类详解。
  18. powerdesigner中cmd模型中多对多_拼多多,在喧嚣中本分前行
  19. 通信——大学专业课程
  20. CentOs中安装并运行rocketmq(报错 ignoring input and appending output to ‘nohup.out’;jps: command not found)

热门文章

  1. svn合并不同树_如何利用SVN合并代码
  2. c语言cstdio头文件,C++头文件----cstdio(stdio.h)
  3. 人脸识别技术在智慧景区的应用有哪些?
  4. C# 程序设计之猜猜看
  5. HTML5常见标签的使用以及DOM操作
  6. 小程序 jwt_使用JWT保护应用程序安全的简介
  7. 量化策略多因子选股之SPSS MODLER建模
  8. CycleGAN实现图像风格迁移的神作
  9. php yac缓存如何清理,yac和memcache性能对比测试
  10. html设置input圆角矩形_css怎么实现按钮圆角样式的展示效果?(示例)