Compose搭档 — ViewModel、LiveData

2024-05-26 17:08

本文主要是介绍Compose搭档 — ViewModel、LiveData,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Compose如虎添翼 -- 搭配ViewModel、LiveData!!!
    • 一、需求一览
    • 二、架构、流程
    • 三、Compose UI开发
      • 3.1、搜索框
      • 3.2、折线图
    • 四、ViewModel 业务开发
    • 五、Compose和ViewModel建立关系
    • 六、总结

Compose如虎添翼 – 搭配ViewModel、LiveData!!!

Compose系列文章,请点原文阅读。原文:是时候学习Compose了!

单纯的使用Compose来进行UI的展示,相信我们已经运用自如了,接下来的文章我们一起搭配其他Jetpack组件,例如LiveData,ViewModel、Room等来了解下Compose在现代化的开发上是多么的简单、舒适!

一、需求一览

我们一起来完成一个需求:首先我们需要一个搜索框,在搜索框中输入城市名,点击键盘回车按钮后请求网络接口获取到该城市的天气信息 – 今日天气,9日天气,并展示在页面上。

大致显示的UI效果及功能如下所示:
GIF 2021-5-25 18-39-21.gif

二、架构、流程

假如使用之前 View + MVP架构 的模式,整体的流程图应该是如下所示:

在这里插入图片描述

那么在 Compose + MVVM架构 中的话,流程图会有什么变化呢?(其实想使用MVI架构,但是又需要加入一定的解释成本,所以后续文章再专门结合MVI做示例吧)

在这里插入图片描述

如上所示,很明显的Activity和Compose在这里只要一个 setContent{} 的关系,后续都是Compose直接和ViewModel之间的交互,Presenter和Model、ViewModel和Model这两层类似,不做赘述。

三、Compose UI开发

接下来我们先使用Compose编写UI,根据需求,我们需要一个搜索框用来输入数据,然后搜索到数据后需要展示今日天气数据、9日天气数据。那么简洁一点,我们就把今日数据用一行文字表示出来,9日温度数据用一个自定义折线图表示出来。

3.1、搜索框

首先是输入框(搜索框),我们使用TextField来完成搜索框功能,通过设置colors相关参数来隐藏其默认自带的下划线指示器,通过shape和modifier参数来控制其圆角边框样式。通过配置keyboardOptions和keyboardActions来获取点击键盘的回车键时触发的事件。 还需要注意一点,这里我们为了在点击回车键后隐藏键盘使用了还在实验阶段的API – LocalSoftwareKeyboardController。整体搜索框代码如下所示:

@ExperimentalComposeUiApi
@Composable
fun SearchView(onClick: (city: String) -> Unit
) {val input = remember {mutableStateOf("")}//键盘控制器,可控制键盘的展示和隐藏val keyboardController = LocalSoftwareKeyboardController.current//输入框圆角设置val corner = 20.dpTextField(value = input.value,onValueChange = {input.value = it},colors = TextFieldDefaults.textFieldColors(//输入框下部的指示线focusedIndicatorColor = Color.Transparent,unfocusedIndicatorColor = Color.Transparent,),//外观配置modifier = Modifier.fillMaxWidth().border(width = 2.dp,color = Color.Black,shape = RoundedCornerShape(corner)),shape = RoundedCornerShape(corner),//键盘配置,输入完毕后隐藏键盘keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),keyboardActions = KeyboardActions(onDone = {keyboardController?.hide()onClick(input.value)}))
}

预览图如下,简简单单一个输入框:
Snipaste_2021-05-25_19-23-59.png

3.2、折线图

接下来是自定义温度折线图,首先我们来分析下9天的数据,那么需要9个点,也就是屏幕需要8等分,然后分别绘制线段和端点就可以了。整体关于Canvas绘制的请查看之前的文章,这里我们需要注意一点就是:绘制的端点是有半径的,我们绘制区域的时候,x轴前后需要留出来这个半径能把首尾的端点全部展示出来,否则首尾的端点只能显示半个。代码如下:

@Composable
fun TempLineChart(modifier: Modifier,weatherDaily: List<WeatherDaily>
) {if (weatherDaily.isEmpty()) {return}val days = weatherDaily.sizeCanvas(modifier = modifier) {//圆点的集合val points: ArrayList<Offset> = ArrayList()//温度的差值(最大温度的差值)val tempMax = weatherDaily.maxOf {it.tempMax.toInt()}val tempMin = weatherDaily.minOf {it.tempMax.toInt()}val diff = tempMax - tempMin//绘制的直线的宽度val lineStrokeWidth = 8f//绘制的最大圆点的直径,注意是半径,绘制时候需要乘以2val pointStrokeWidth = 16fval path = Path()//起点位置val startX = pointStrokeWidthval startY = size.height//平均每天的步长,需剔除圆点的宽度val xOffset = (size.width - pointStrokeWidth * 2) / (days - 1)val endX = size.width - pointStrokeWidthpath.moveTo(startX, startY)var lastOffset: Offset? = nullfor ((index, weatherDailyBean) in weatherDaily.withIndex()) {val x = startX + xOffset * indexval y =startY - (size.height / (diff + 2) * ((weatherDailyBean.tempMax.toInt() - tempMin) + 1))val offset = Offset(x, y)points.add(offset)//路径path.lineTo(x, y)//绘制直线if (lastOffset != null) {drawLine(color = Color(0xFF357AFF),start = lastOffset,end = offset,strokeWidth = lineStrokeWidth,)}lastOffset = offset}path.lineTo(endX, startY)path.close()//绘制路径drawPath(path = path,brush = Brush.verticalGradient(colors = arrayListOf(Color(0x80357AFF), Color(0x00000000))),)//绘制蓝色圆点drawPoints(pointMode = PointMode.Points,color = Color(0xFF357AFF),strokeWidth = pointStrokeWidth * 2,points = points,cap = StrokeCap.Round,)//绘制白色圆点drawPoints(pointMode = PointMode.Points,color = Color.White,strokeWidth = pointStrokeWidth,points = points,cap = StrokeCap.Round,)}
}

OK,然后造几条伪数据,我们使用@Preview来预览下显示效果:

@Preview
@Composable
fun TempLineChartPreview() {val weatherDailyList = ArrayList<WeatherDaily>()for (i in 1..9) {weatherDailyList.add(WeatherDaily(tempMax = i.toString()))}TempLineChart(modifier = Modifier.height(200.dp).fillMaxWidth(),weatherDailyList)
}

Snipaste_2021-05-25_19-24-26.png


四、ViewModel 业务开发

至此,我们单独的UI已经编写完毕了,接下来是ViewModel的部分,网络请求这块无疑是Retrofit套餐,但是Retrofit和Compose没有任何关系,所以这里我们暂时不花篇幅讲解其使用方式,直接使用伪数据来代替网络请求结果,后续文章我们会结合Hilt来示例Retrofit、Room等相关知识。ViewModel相关代码如下:

class MainViewModel : ViewModel() {/*** 城市名*/private val _cityName = MutableLiveData<String>()/*** 对外单独暴漏修改城市名方法*/fun updateCityName(name: String) {_cityName.value = name}/*** 当日天气【当_cityName值变更的时候,这里会响应】*/val weatherNow: LiveData<String> = Transformations.switchMap(_cityName) {MutableLiveData(" ${_cityName.value} 地区,今日天气好的不能再好了!")}/*** n天天气【当_cityName值变更的时候,这里会响应】*/val weatherDays: LiveData<List<WeatherDaily>> = Transformations.switchMap(_cityName) {val weatherDailyList = ArrayList<WeatherDaily>()for (i in 1..9) {val temp = (15..20).random()weatherDailyList.add(WeatherDaily(tempMax = temp.toString()))}MutableLiveData(weatherDailyList)}
}

注意:我们使用了Transformations类,当_cityName的值变化的时候, switchMap( _cityName ) 会响应,我们处理过后返回一个新的LiveData的值,weatherNow和weatherDays这两个变量就会被赋值。

【其实这里的代码设计方式再深入想一下,好像又能感受到一丝 MVI Intent的思想。】


五、Compose和ViewModel建立关系

Compose UI和ViewModel都搞定了,那么他们之间如何像上文流程图中表示的那样可以建立联系呢?其实官方给我们提供了一个库:androidx.lifecycle:lifecycle-viewmodel-compose:$latestVersion,该库提供了一个**viewModel()**函数,可以直接在@Composable 函数中访问到相关ViewModel的实例,例如:

@ExperimentalComposeUiApi
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {val weatherNow = viewModel.weatherNow.observeAsState()val weatherDays = viewModel.weatherDays.observeAsState(arrayListOf())}

如上,我们在参数中直接使用viewModel()来获取MainViewModel实例,而在MainScreen()函数中我们还使用到了一个 observeAsState() 函数,使用该函数也需要引用一个扩展库:androidx.compose.runtime:runtime-livedata:$latestVersion,该函数的作用就是将ViewModel提供的LiveData数据转换为Compose需要的State数据。

当LiveData数据更新后,LiveData转换为State,而Compose会根据State数据来自行刷新,所以将之前的UI控件组合起来,再将State数据设置进去,相关代码如下所示:

@ExperimentalComposeUiApi
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {val weatherNow = viewModel.weatherNow.observeAsState()val weatherDays = viewModel.weatherDays.observeAsState(arrayListOf())Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {Spacer(modifier = Modifier.height(20.dp))SearchView(onClick = {viewModel.updateCityName(it)})Spacer(modifier = Modifier.height(20.dp))Text(text = weatherNow.value ?: "")TempLineChart(modifier = Modifier.height(200.dp).fillMaxWidth(),weatherDaily = weatherDays.value)}
}

OK,至此整体就大功告成了,运行下代码试试吧,能不能达到如下效果呢?
GIF 2021-5-25 18-39-21.gif

六、总结

整体的话,重点在于Compose和ViewModel的结合、以及LiveData和State的使用。这其中我们还要注意Compose的架构思想:

  • 事件向上传递,例如搜索框的回车事件,暴漏出来给上层处理;
  • 状态向下传递,例如网络请求结果数据等向UI层传递显示,可以封装成网络请求中、请求成功、请求失败等状态向UI传递;

还有一个也比较重要:

  • 单一信任源,上文中没有明显的示例,但是你可以观察到TextField中输入的数据是根据 input 的值来进行变化的。举个View中的例子,比如CheckBox,当你点击的时候,状态会立即进行改变,此时如果网络请求失败了,我们还需要把CheckBox的显示状态重置。但是在Compose中,CheckBox的显示状态改变和TextField一样,只有input的值变化了,它才变化。也就是说CheckBox需要订阅一个变量,你只有请求网络成功后或者失败后更改此变量,CheckBox才会根据此变量更改显示状态。

这篇关于Compose搭档 — ViewModel、LiveData的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1005077

相关文章

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)

1、MVC MVC(Model-View-Controller) 是一种常用的架构模式,用于分离应用程序的逻辑、数据和展示。它通过三个核心组件(模型、视图和控制器)将应用程序的业务逻辑与用户界面隔离,促进代码的可维护性、可扩展性和模块化。在 MVC 模式中,各组件可以与多种设计模式结合使用,以增强灵活性和可维护性。以下是 MVC 各组件与常见设计模式的关系和作用: 1. Model(模型)

docker-compose安装和简单使用

本文介绍docker-compose的安装和使用 新版docker已经默认安装了docker-compose 可以使用docker-compose -v 查看docker-compose版本 如果没有的话可以使用以下命令直接安装 sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-c

Docker Compose使用手册

Docker Compose是一个比较简单的docker容器的编配工具,以前的名称叫Fig,由Orchard团队开发的开源Docker编配工具,在2014年被Docker公司收购,Docker Compose是使用Python语言开发的一款docker编配工具。使用Docker Compose,可以用一个yml文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose称这些容器为

【docker】基于docker-compose 安装elasticsearch + kibana + ik分词器(8.10.4版本)

记录下,使用 docker-compose 安装 Elasticsearch 和 Kibana,并配置 IK 分词器,你可以按照以下步骤进行。此过程适用于 Elasticsearch 和 Kibana 8.10.4 版本。 安装 首先,在你的工作目录下创建一个 docker-compose.yml 文件,用于配置 Elasticsearch 和 Kibana 的服务。 version:

Docker-Compose for Linux安装

Docker-Compose for Linux安装 1.前言2.安装Docker-Compose 1.前言 我们为什么要使用docker-compose? 我们运行一个docker镜像,需要添加大量的参数。 现在我们可以通过docker-compose编写这些参数。 Docker-Compose可以帮助我们批量管理这些容器。 我们只需要通过一个docker-compos

docker和docker-compose安装脚本

1.docker安装脚本 1.1创建脚本文件 touch install_docker.shchmod 777 install_docker.shcat > install_docker.sh << 'EOF'#!/bin/bash# 删除现有的 Dockerecho -e "========== 1. 删除现有 Docker ================\n\n"sudo yu

《Linux运维总结:基于X86_64+ARM64架构CPU使用docker-compose一键离线部署consul 1.18.1容器版分布式ACL集群》

总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:《Linux运维篇:Linux系统运维指南》 一、部署背景 由于业务系统的特殊性,我们需要面向不通的客户安装我们的业务系统,而作为基础组件中的consul 针对不同的客户环境需要多次部署集群,作为一个运维工程师,提升工作效率也是工作中的重要一环。所以我觉得有必要针对 x86_64 + ARM64 CPU架构cons

基础学习之——Docker Compose的安装和使用

Docker Compose是一个用于定义和管理多个Docker容器的工具。它使用YAML文件来配置应用程序的服务、网络和卷等等。下面是Docker Compose的安装、配置和使用方式的详细说明: 安装Docker Compose: 首先,确保已经安装了Docker引擎。可以参考官方文档安装Docker:https://docs.docker.com/install/然后,下载适合您操作系统

09-02 周一 Ubuntu上使用docker-compose部署elasticsearch和kibana服务

09-02 周一 Ubuntu上部署elasticsearch和kibana服务 时间版本修改人描述2024年9月2日11:13:54V0.1宋全恒新建文档 简介  由于组里需要提供一个简易的环境来部署一套服务,可以通过接口进行数据的存储和检索,因此,直接部署一套ES服务来充当这样的功能,本文主要是负责记录整个环境的搭建过程。 步骤 确定docker命令可以使用 songquanh