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(四)下拉刷新、复杂数据、网格布局、文字样式相关推荐

  1. android新闻app实现下拉刷新,Android实例_当监听类有数据更新时下拉刷新

    之前两篇文章分别介绍了OnScrollListener的实现和ContentProvider监听数据的变化,下面我们就结合者两个知识点实现一个小项目 项目需求 使用当ContentProvider监听 ...

  2. Android PullToRefresh (ListView GridView 下拉刷新) 使用详解

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38238749,本文出自:[张鸿洋的博客] 群里一哥们今天聊天偶然提到这个git ...

  3. android禁止下拉刷新,Android开发之无痕过渡下拉刷新控件的实现思路详解

    相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下 ...

  4. 微信小程序 通过wx.redirectTo,实现单页面刷新效果 & 下拉刷新页面数据效果

    微信小程序 通过wx.redirectTo,实现单页面刷新效果 & 下拉刷新页面数据效果 一: 使用 wx.redirectTo(),实现页面刷新数据效果 API说明: 关闭当前页面,跳转到应 ...

  5. Android Compose 新闻App(二)ViewModel、Hlit、数据流

    Compose 新闻App(二)ViewModel.Hlit.数据流 前言 正文 一.添加依赖 ① 添加Hilt依赖 ②添加ViewModel依赖 二.Hilt使用 三.ViewModel使用 四.数 ...

  6. android 自定义顶部,Android自定义实现顶部粘性下拉刷新效果

    本文实例为大家分享了Android实现顶部粘性下拉刷新效果的具体代码,供大家参考,具体内容如下 activity_view_mv代码 xmlns:android="http://schema ...

  7. Android Compose 新闻App(一)网络框架搭建

    Compose 新闻App(一)网络框架搭建 前言 正文 一.项目创建 二.依赖配置 三.数据API 四.网络框架构建 五.项目配置 六.网络请求 七.源码 前言   要去学习新的知识,光是简单的使用 ...

  8. Android自定义控件之仿美团下拉刷新

    android美团下拉刷新控件自定义控件 目录(?)[+]   美团的下拉刷新分为三个状态:  第一个状态为下拉刷新状态(pull to refresh),在这个状态下是一个绿色的椭圆随着下拉的距离动 ...

  9. Android 学习笔记之九 下拉刷新

    下拉刷新控件终结者:PullToRefreshLayout 转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 源 ...

最新文章

  1. GE数字化重塑的启示:调整阵型,再战工业互联网!
  2. SpringBoot 启动错误搜集
  3. MySQL 5.6.11 GA 发布
  4. Oracle 11g服务端的安装和配置
  5. react全局方法_前端面试题 ---react
  6. Solr 部分 局部字段修改 更新 删除
  7. Java生成指定范围随机数的方法
  8. postgreSQL源码分析——索引的建立与使用——各种索引类型的管理和操作(2)
  9. 在线Javascript加密混淆工具
  10. case when的判断顺序_Sql 中的if 判断 case... when
  11. 【持续更新】总结国内外图形学物理模拟相关学者和网站
  12. Acer 4750 安装黑苹果_傻瓜式黑苹果安装神器
  13. 科大讯飞指定录音文件转文字(异步)
  14. android adb 电池电量,adb 查看电池状态信息。详解
  15. 100个世界上鲜为人知的“常识”
  16. xinetd使用指南
  17. Navigation测试常用网址导航工具
  18. java实现数据挖掘_数据挖掘Apriori算法的java实现
  19. codefoces-A. Pens and Pencils
  20. 内网渗透----常见后门

热门文章

  1. 社团团购背后的巨头操盘手,各显神通的他们能啃下这块硬骨头吗?
  2. 网吧加速浏览器解压软件最新免费版
  3. 江苏交通学习网自动播放下一页
  4. 1、SAP BI概览
  5. python安装urllib2_python urllib2篇
  6. hikariCP连接池+oracle/sqlserver
  7. 玩幻宠大冒险显示服务器连接失败,别客汇星球之幻宠大冒险首测 体验五星级指尖竞技...
  8. 【实战类】Hog SVM进行图像分类任务
  9. oracle创建索引
  10. 邮件那些事4—浅析伪造发信人的原理与识别