Android Compose 新闻App(四)下拉刷新、复杂数据、网格布局、文字样式
Compose 新闻App(四)下拉刷新、复杂数据、网格布局、文字样式
- 前言
- 正文
- 一、下拉刷新
- ① 添加依赖
- ② 使用
- ③ 样式更改
- 二、刷新数据
- 三、复杂数据
- 四、复杂列表
- ① 更改返回数据
- ② 增加item
- ③ 嵌套
- 五、网格布局
- 六、修改样式
- 七、源码
前言
在上一篇文章中我们进行数据的存储和缓存的使用,这里我们进一步去优化这个业务。
正文
首先我们想一个问题,那就是假如我一天不只是请求一次网络接口呢?要怎么办呢?难道我去应用管理中去清除本地数据然后再打开应用吗?那太傻了,那么就可以通过刷新的方式去更新当前的数据,同时这个数据还能存到本地数据库,这个业务看起来就更人性化一些。
一、下拉刷新
通过标题就知道我要说什么内容了,在之前的Android开发中下拉刷新是常用的功能,而在Compose中也如此,只不过使用方式更简单一些,首先我们添加依赖版本。
① 添加依赖
打开项目的build.gradle,增加如下版本代码:
accompanist_version = '0.24.4-alpha'
目前我们只会用accompanist库中的刷新组件,到后面我们可能会用到accompanist库中的其他组件,因此我这里定义了accompanist_version,当然你也可以不定义。
增加位置如下图所示:
然后打开app下的build.gradle,增加如下依赖代码:
implementation "com.google.accompanist:accompanist-swiperefresh:$accompanist_version"
增加位置如下图所示:
② 使用
使用其实非常的简单,下面我们改动一下MainActivity.kt中的BodyContent()函数,如下图所示:
原来这里只有一个LazyColumn,现在我在它的上面增加了一个SwipeRefresh,然后里面有两个必备的属性值,state和onRefresh,state是表示刷新状态,onRefresh表示刷新后执行的操作,这里我就弹一个Toast。下面我们运行一下看看:
③ 样式更改
刚才是最基本的使用,功能基本上有了,那么我们改一下它的样式。
这里配置了一指示器的样式:refreshTriggerDistance表示刷新触发的距离,scale表示刷新View的大小动画,默认是关闭的,我们打开。然后就是背景颜色,还有就是形状样式,下面再看看运行的效果:
我这里是放的很慢去进行的,为的就是看清楚这个动画效果。
二、刷新数据
现在对于下拉刷新控件上的说明就结束了,我们要进入使用的环节了,实际上使用就是把onRefresh中执行方法换成我们实际的业务逻辑就行了,只不过通过下拉刷新来串联这个业务。
刷不刷新数据需要一个变量来控制,因此首先我们需要改动EpidemicNewsRepository.kt中的getEpidemicNews()函数,如下图所示:
这里就是增加一个参数,把这个参数作为是否需要请求网络数据的标准之一,当前没有刷新并且不是今天第一次请求网络,则从本地获取,如果有刷新,就从网络中请求数据。
然后我们需要改一下MainViewModel.kt
原来这里里面只有一行代码现在则多了一些,这里的代码很好理解,这里暴露一个方法给外部去调用,当getNews()函数的参数有改变时,就会触发repository.getEpidemicNews(it),然后页面上继续观察result的变化,有变化就会更新页面数据。
下面回到MainActivity.kt中,修改一下initData()函数中的方法,如下图所示:
这里我去掉了那个没有什么必要的临时变量,下面我们只需要在BodyContent()函数中的onRefresh的函数体中调用viewModel.getNews(true)方法即可实现下拉刷新功能。
下面我们再运行一下:
OK,这个地方就完成了。
三、复杂数据
在实际开发中,很多都是A表中含有B表,而如果不需要建立那么多表,我们可以通过Room去处理,例如Desc数据类
它里面含有GlobalStatistics和ForeignStatistics,我们要在数据库中添加的话那么怎么样去操作呢?可以让这两个数据类成为Desc数据类的列。添加@Embedded注解,同时给Desc加上@Entity注解,同时主键也要添加。
下面我们再看GlobalStatistics和ForeignStatistics,他们里面的字段大部分相同,而在Room中不运行字段相同的情况,因此我们需要改一下列名,代码如下所示:
data class GlobalStatistics(@ColumnInfo(name = "global_currentConfirmedCount")val currentConfirmedCount: Int = 0,@ColumnInfo(name = "global_confirmedCount")val confirmedCount: Int = 0,@ColumnInfo(name = "global_curedCount")val curedCount: Int = 0,@ColumnInfo(name = "global_currentConfirmedIncr")val currentConfirmedIncr: Int = 0,@ColumnInfo(name = "global_confirmedIncr")val confirmedIncr: Int = 0,@ColumnInfo(name = "global_curedIncr")val curedIncr: Int = 0,@ColumnInfo(name = "global_deadCount")val deadCount: Int = 0,@ColumnInfo(name = "global_deadIncr")val deadIncr: Int = 0,@ColumnInfo(name = "global_yesterdayConfirmedCountIncr")val yesterdayConfirmedCountIncr: Int = 0
)data class ForeignStatistics(@ColumnInfo(name = "foreign_currentConfirmedCount")val currentConfirmedCount: Int = 0,@ColumnInfo(name = "foreign_confirmedCount")val confirmedCount: Int = 0,@ColumnInfo(name = "foreign_curedCount")val curedCount: Int = 0,@ColumnInfo(name = "foreign_currentConfirmedIncr")val currentConfirmedIncr: Int = 0,@ColumnInfo(name = "foreign_suspectedIncr")val suspectedIncr: Int = 0,@ColumnInfo(name = "foreign_confirmedIncr")val confirmedIncr: Int = 0,@ColumnInfo(name = "foreign_curedIncr")val curedIncr: Int = 0,@ColumnInfo(name = "foreign_deadCount")val deadCount: Int = 0,@ColumnInfo(name = "foreign_deadIncr")val deadIncr: Int = 0,@ColumnInfo(name = "foreign_suspectedCount")val suspectedCount: Int = 0
)
因为Desc这里只是一个数据类,而不是List,所以我们每次保存和添加数据都是用同一个指定的ID。那么我们就需要将Desc的id改为var,这样后面我们就直接改这个id。
@PrimaryKey var id: Int = 0,
数据表改好之后,还有一个简单的方法,我们去dao包下增加一个DescDao接口,里面的代码如下:
@Dao
interface DescDao {@Query("SELECT * FROM `desc` WHERE id LIKE :id LIMIT 1")suspend fun getDesc(id: Int = 1): Desc@Insertsuspend fun insert(desc: Desc?)@Query("DELETE FROM `desc`")suspend fun deleteAll()
}
这里很好理解,查询的时候就查询id=1的数据,那么我们在插入数据的时候就要设置desc的id为1, 最后在AppDatabase中配置
看到这里是不是觉得很奇怪,这样肯定会报错,为什么呢?因为我之前最开始添加了一个表,之前的版本就是1,那么我这里新增了Desc表,那么对应的数据库版本就要迁移,你可以理解为升级,同时我们需要去添加一个Migrations,在这里去写新增数据表的SQL,这是很麻烦的,那么在调试中如果你只是自己调试而不需要发给别人的话,你可以这样做,就是把你现在的应用卸载,你再重新安装就可以了。省时省力,但是如果你是线上的项目那你还是老老实实的写迁移数据库的SQL,并且做好测试,当然了我们现在这样做只是方便开发测试,实际中不推荐你这么做,切记,切记。
下面回到EpidemicNewsRepository中,修改一下saveNews()函数,代码如下:
这里我们直接拿到newslistItem,因为news和desc都是在newslistItem中,注意看在插入desc的时候将id设置为1,那么我们在查询的时候要怎么做呢?修改一下getLocalForNews()函数,
你会发现我这里并没有传入参数,因为用了缺省值,当然了你也可以传入一个其他值,那么你在出入的时候也要做更改。
现在数据这一块就可以了,下面我们来做UI这一块的内容,需要用到Desc表中的数据。
四、复杂列表
① 更改返回数据
在之前主页面中就是显示一个数据列表,而没有其他的东西了,我们需要的desc和news属于同一级,因此我们需要上一级的数据。那么就需要改一下
看这个图应该就很好理解了,我们先拿到NewslistItem,然后再通过NewslistItem拿到news和desc,然后在BodyContent()函数中增加一个参数。
好了,到这里我们的数据就到了它应该去的地方,下面我们打印一下:
这里的Gson在前面就已经添加了依赖库,没有注意到的看前面的内容或者看源码,下面运行一下,看有没有日志打印:
很好,有日志,那么说明数据库没有问题,下面进行数据的显示。
② 增加item
下面我们在之前的列表上方再增加一个item,来看看怎么增加。
首先我们增加了一个item,然后再item里面去设置一个Card,然后设置宽度、高度、内填充、阴影、背景颜色。也就是说在最上方增加一个卡片式布局,下面我们来看布局里面的内容。
在Card中有一个Row,那么里面的内容就是横向,然后Row里面放了两个Column,表示里面有两个纵向,两个Column的设置一样,这里要注意的是weight(1f),表示权重,现在两个都是Column都是1f就是各占Row的50%,Column里面的其他两个配置就是Colum的内容上下居中。
那么我们再来看Column里的内容
这里的就很好理解了,基本上不用说什么了,如果你需要知道这些currentConfirmedIncr的含义,就去天行的API上去看,哪里有,下面我们运行一下:
你会看到一个列表有两个内容,内容不一样,但还是同一个列表,并且你的下拉刷新一样有效。
③ 嵌套
这里显示两个内容有点少,我们改一下
上面截图中的代码如下:
item {Card(modifier = Modifier.fillMaxWidth().padding(8.dp),elevation = 2.dp,backgroundColor = Color.White) {Column {Row(modifier = Modifier.padding(12.dp)) {Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,//设置垂直居中对齐horizontalAlignment = Alignment.CenterHorizontally//设置水平居中对齐) {Text(text = "现存确诊人数")Text(text = desc.currentConfirmedCount.toString(), fontSize = 24.sp, fontWeight = FontWeight.Bold)Text(text = "较昨日${desc.currentConfirmedIncr}")}Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text(text = "累计确诊人数")Text(text = desc.confirmedCount.toString(), fontSize = 24.sp, fontWeight = FontWeight.Bold)Text(text = "较昨日${desc.currentConfirmedIncr}")}}Row(modifier = Modifier.padding(12.dp)) {Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,//设置垂直居中对齐horizontalAlignment = Alignment.CenterHorizontally//设置水平居中对齐) {Text(text = "累计治愈人数")Text(text = desc.curedCount.toString(), fontSize = 24.sp, fontWeight = FontWeight.Bold)Text(text = "较昨日${desc.curedIncr}")}Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text(text = "累计死亡人数")Text(text = desc.deadCount.toString(), fontSize = 24.sp, fontWeight = FontWeight.Bold)Text(text = "较昨日${desc.deadIncr}")}}}}}
我们把固定高度改了,让它自适应里面的内容高度,下面再运行一下:
如果这个代码写在BodyContent()函数中,那么会看起来代码很多,我们可以抽离一下,新增一个descItem函数,代码如下图所示:
五、网格布局
从上面这里的代码我们已经实现了功能,但是会不会看上去不太智能呢?如果每一次添加都这样,那就太蠢了,因此我们可以用到网格布局。Compose的网格布局有横向的有纵向的,但还不稳定,因此就需要手动去写,这里可以这样去写,首先在MainActivity.kt中创建两个数据类
data class DescItem(var title: String, var current: Int, var yesterday: Int)data class GroupItem(val descItem: DescItem?,val isEmpty: Boolean)
我们实际需要的数据并不太多,然后我们可以写一个descItemPlus()函数
private fun LazyListScope.descItemPlus(desc: Desc) {}
首先因为数据是组装的,不是列表所以手动构建一个列表。
private fun LazyListScope.descItemPlus(desc: Desc) {//构建一个DescItemListval descList = mutableListOf<DescItem>().apply {add(DescItem("现存确诊人数", desc.currentConfirmedCount, desc.currentConfirmedIncr))add(DescItem("累计确诊人数", desc.confirmedCount, desc.confirmedIncr))add(DescItem("累计治愈人数", desc.curedCount, desc.curedIncr))add(DescItem("累计死亡人数", desc.deadCount, desc.deadIncr))add(DescItem("现存无症状人数", desc.seriousCount, desc.seriousIncr))}
}
现在数据源就有了,然后就是根据这个数据源去计算网格中的行和列。在descItemPlus函数中增加如下代码:
//网格Itemsval gradItems = mutableListOf<List<GroupItem>>()var index = 0//网格是行与列组成,显示2列val columnNum = 2//计算显示几行val rowNum = ceil(descList.size.toFloat() / columnNum).toInt()//遍历行for (i in 0 until rowNum) {val rowItems = mutableListOf<GroupItem>()//遍历列for (j in 0 until columnNum) {if (index.inc() <= descList.size) {rowItems.add(GroupItem(descList[index++],false))}}//如果未填充满,则显示占位val itemEmpty = columnNum - rowItems.sizefor (j in 0 until itemEmpty) {rowItems.add(GroupItem(null,true))}gradItems.add(rowItems)}
最后在descItemPlus()函数中我们显示每一个item。
//显示数据items(gradItems) { gradItem ->Row {for (gird in gradItem) {if (gird.isEmpty) {Box(modifier = Modifier.weight(1f))} else {Box(modifier = Modifier.weight(1f)) {Card(modifier = Modifier.fillMaxWidth().padding(8.dp),elevation = 2.dp,backgroundColor = Color.White) {val descItem = gird.descItem!!Column(modifier = Modifier.padding(12.dp),verticalArrangement = Arrangement.Center,//设置垂直居中对齐horizontalAlignment = Alignment.CenterHorizontally//设置水平居中对齐) {Text(text = descItem.title)Text(text = descItem.current.toString(),fontSize = 24.sp,fontWeight = FontWeight.Bold)Text(text = "较昨日${descItem.yesterday}")}}}}}}}
这样就可以实现功能了,下面我们调用一下:
然后运行一下:
实际中根据自己的需求去更改使用的方式,,这个descItemPlus()函数的代码会保留,这里我还是用之前的descItem()函数,因为我需要去更改文字的不同样式。
六、修改样式
首先在colors.xml中增加一些色值
<color name="red">#FC3538</color><color name="dark_red">#B00000</color><color name="green">#1BB394</color><color name="gray_black">#656565</color><color name="gray">#989898</color>
下面我们来修改一下descItem()函数中的控件样式。
这里先来改第一个,这里修改了文字的大小,然后设置了颜色,和填充,最主要的是下面这个buildAnnotatedString,它可以对一个Text中的不同内容做不同的样式设置,然后这里还有一个拓展函数addSymbols(),代码如下:
fun Int.addSymbols(): String = if (this > 0.0 && this != 0) "+$this" else "$this"
可以直接写在MainActivity.kt,也可以单独建一个kotlin文件去写,这个拓展函数主要就是如果我们的int数据是正数,还要大于零,就添加一个+,负责就是原来的数据,返回String。那么其他的都可以照着改一下,我这里贴一下descItem()函数的代码:
private fun LazyListScope.descItem(desc: Desc) {item {Card(modifier = Modifier.fillMaxWidth().padding(8.dp),elevation = 2.dp,backgroundColor = Color.White) {Column {Row(modifier = Modifier.padding(12.dp)) {Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,//设置垂直居中对齐horizontalAlignment = Alignment.CenterHorizontally//设置水平居中对齐) {Text(text = "现存确诊人数", fontSize = 12.sp)Text(text = desc.currentConfirmedCount.toString(),fontSize = 28.sp,fontWeight = FontWeight.Bold,color = colorResource(id = R.color.red),modifier = Modifier.padding(0.dp, 4.dp))Text(buildAnnotatedString {withStyle(style = SpanStyle(fontSize = 12.sp, color = colorResource(id = R.color.gray))) {append("较昨日 ")}withStyle(style = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Bold)) {append(desc.currentConfirmedIncr.addSymbols())}})}Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text(text = "累计确诊人数", fontSize = 12.sp)Text(text = desc.confirmedCount.toString(),fontSize = 28.sp,fontWeight = FontWeight.Bold,color = colorResource(id = R.color.dark_red),modifier = Modifier.padding(0.dp, 4.dp))Text(buildAnnotatedString {withStyle(style = SpanStyle(fontSize = 12.sp, color = colorResource(id = R.color.gray))) {append("较昨日 ")}withStyle(style = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Bold)) {append(desc.confirmedIncr.addSymbols())}})}}Row(modifier = Modifier.padding(12.dp)) {Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,//设置垂直居中对齐horizontalAlignment = Alignment.CenterHorizontally//设置水平居中对齐) {Text(text = "累计治愈人数", fontSize = 12.sp)Text(text = desc.curedCount.toString(),fontSize = 28.sp,fontWeight = FontWeight.Bold,color = colorResource(id = R.color.green),modifier = Modifier.padding(0.dp, 4.dp))Text(buildAnnotatedString {withStyle(style = SpanStyle(fontSize = 12.sp, color = colorResource(id = R.color.gray))) {append("较昨日 ")}withStyle(style = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Bold)) {append(desc.curedIncr.addSymbols())}})}Column(modifier = Modifier.fillMaxSize().weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text(text = "累计死亡人数", fontSize = 12.sp)Text(text = desc.deadCount.toString(),fontSize = 28.sp,fontWeight = FontWeight.Bold,color = colorResource(id = R.color.gray_black),modifier = Modifier.padding(0.dp, 4.dp))Text(buildAnnotatedString {withStyle(style = SpanStyle(fontSize = 12.sp, color = colorResource(id = R.color.gray))) {append("较昨日 ")}withStyle(style = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Bold)) {append(desc.deadIncr.addSymbols())}})}}}}}
}
下面运行一下:
七、源码
如果你觉得代码对你有帮助的话,不妨Fork或者Star一下~
GitHub:GoodNews
CSDN:GoodNews_4.rar
Android Compose 新闻App(四)下拉刷新、复杂数据、网格布局、文字样式相关推荐
- android新闻app实现下拉刷新,Android实例_当监听类有数据更新时下拉刷新
之前两篇文章分别介绍了OnScrollListener的实现和ContentProvider监听数据的变化,下面我们就结合者两个知识点实现一个小项目 项目需求 使用当ContentProvider监听 ...
- Android PullToRefresh (ListView GridView 下拉刷新) 使用详解
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38238749,本文出自:[张鸿洋的博客] 群里一哥们今天聊天偶然提到这个git ...
- android禁止下拉刷新,Android开发之无痕过渡下拉刷新控件的实现思路详解
相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下 ...
- 微信小程序 通过wx.redirectTo,实现单页面刷新效果 & 下拉刷新页面数据效果
微信小程序 通过wx.redirectTo,实现单页面刷新效果 & 下拉刷新页面数据效果 一: 使用 wx.redirectTo(),实现页面刷新数据效果 API说明: 关闭当前页面,跳转到应 ...
- Android Compose 新闻App(二)ViewModel、Hlit、数据流
Compose 新闻App(二)ViewModel.Hlit.数据流 前言 正文 一.添加依赖 ① 添加Hilt依赖 ②添加ViewModel依赖 二.Hilt使用 三.ViewModel使用 四.数 ...
- android 自定义顶部,Android自定义实现顶部粘性下拉刷新效果
本文实例为大家分享了Android实现顶部粘性下拉刷新效果的具体代码,供大家参考,具体内容如下 activity_view_mv代码 xmlns:android="http://schema ...
- Android Compose 新闻App(一)网络框架搭建
Compose 新闻App(一)网络框架搭建 前言 正文 一.项目创建 二.依赖配置 三.数据API 四.网络框架构建 五.项目配置 六.网络请求 七.源码 前言 要去学习新的知识,光是简单的使用 ...
- Android自定义控件之仿美团下拉刷新
android美团下拉刷新控件自定义控件 目录(?)[+] 美团的下拉刷新分为三个状态: 第一个状态为下拉刷新状态(pull to refresh),在这个状态下是一个绿色的椭圆随着下拉的距离动 ...
- Android 学习笔记之九 下拉刷新
下拉刷新控件终结者:PullToRefreshLayout 转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 源 ...
最新文章
- GE数字化重塑的启示:调整阵型,再战工业互联网!
- SpringBoot 启动错误搜集
- MySQL 5.6.11 GA 发布
- Oracle 11g服务端的安装和配置
- react全局方法_前端面试题 ---react
- Solr 部分 局部字段修改 更新 删除
- Java生成指定范围随机数的方法
- postgreSQL源码分析——索引的建立与使用——各种索引类型的管理和操作(2)
- 在线Javascript加密混淆工具
- case when的判断顺序_Sql 中的if 判断 case... when
- 【持续更新】总结国内外图形学物理模拟相关学者和网站
- Acer 4750 安装黑苹果_傻瓜式黑苹果安装神器
- 科大讯飞指定录音文件转文字(异步)
- android adb 电池电量,adb 查看电池状态信息。详解
- 100个世界上鲜为人知的“常识”
- xinetd使用指南
- Navigation测试常用网址导航工具
- java实现数据挖掘_数据挖掘Apriori算法的java实现
- codefoces-A. Pens and Pencils
- 内网渗透----常见后门