Android Compose——ScrollableTabRow和LazyColumn同步滑动

2024-01-07 10:20

本文主要是介绍Android Compose——ScrollableTabRow和LazyColumn同步滑动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android Compose——ScrollableTabRow和LazyColumn同步滑动

  • 效果
  • 数据
  • 实现
    • Tab
    • List列表
  • 如何同步实现?
    • 监听列表滑动变化
    • 计算列表子项索引位置
    • Tab滑动

效果

Demo简述:此Demo所实现的效果为当滑动List列表时,所对应的Tab相对应进行滑动切换,为了模拟复杂数据环境,通过不同类别的数据进行操作,通过sealed或者enum结合when 进行区分不同的类。具体效果如下视频所示。

数据

下列通过AnimalVegetableFruitVehicle四个不同的类用来模拟数据环境

data class Animal(val content:String)
data class Vegetable(val content:String)
data class Fruit(val content:String)
data class Vehicle(val content:String)data class Type(val animals:List<Animal>,val vegetables:List<Vegetable>,val fruits:List<Fruit>,val vehicles:List<Vehicle>,val categories:List<String>
)

其中categories记录的是所有存在的类别列表标题,其余四个分别为对应的Tab的数据列表

val type = Type(animals = listOf(Animal("子鼠"),Animal("丑牛"),Animal("寅虎"),Animal("卯兔"),Animal("辰龙"),Animal("已蛇"),Animal("午马"),Animal("未羊"),Animal("申猴"),Animal("酉鸡"),Animal("戌狗"),Animal("亥猪")),vegetables = listOf(Vegetable("白萝卜"),Vegetable("红萝卜"),Vegetable("黄萝卜"),Vegetable("绿萝卜"),Vegetable("紫萝卜"),Vegetable("橙萝卜"),Vegetable("黑萝卜"),Vegetable("粉红萝卜"),Vegetable("蓝萝卜"),Vegetable("青萝卜"),Vegetable("灰萝卜"),Vegetable("棕萝卜"),Vegetable("朱砂萝卜"),Vegetable("胭脂萝卜"),),fruits = listOf(Fruit("苹果"),Fruit("香蕉"),Fruit("海棠"),Fruit("樱桃"),Fruit("枇杷"),Fruit("山楂"),Fruit("梨"),Fruit("李子"),Fruit("蓝莓"),Fruit("黑莓"),Fruit("西瓜"),Fruit("火龙果"),Fruit("榴莲"),),vehicles = listOf(Vehicle("飞机"),Vehicle("火箭"),Vehicle("坦克"),Vehicle("共享单车"),Vehicle("汽车"),Vehicle("摩托车"),Vehicle("三轮车"),Vehicle("自行车"),Vehicle("电动车"),Vehicle("高铁"),Vehicle("马车"),Vehicle("驴车"),Vehicle("出租车"),Vehicle("地铁"),),categories = listOf("Animals","Vegetables","Fruits","Vehicles",)
)

实现

lazyListTabSync是一个封装的组合函数,其中传入的参数是一个列表,上述我们建立了四个类别数据,则此参数传入应为mutableListOf(0,1,2,3),与下列所传入的参数效果一致

 val (selectedTabIndex, setSelectedTabIndex, listState) = lazyListTabSync(type.categories.indices.toList())

除了上述用法之外,还可以像下面一样使用,但是所传入的tabsCount个数不能小于种类个数(mutableListOf(0,1,2,3)的个数)

val (selectedTabIndex, setSelectedTabIndex, listState) = tabSyncMediator(mutableListOf(0, 2, 4), tabsCount = 3, lazyListState = rememberLazyListState(), smoothScroll = true, )

Tab

Tab的实现主要在于当前Tab的位置和Tab的点击事件

@Composable
fun MyTabBar(type: Type,selectedTabIndex: Int,onTabClicked: (index: Int, type: String) -> Unit
) {ScrollableTabRow(selectedTabIndex = selectedTabIndex,edgePadding = 0.dp) {type.categories.forEachIndexed { index, category ->Tab(selected = index == selectedTabIndex,onClick = { onTabClicked(index, category) },text = { Text(category) })}}
}

List列表

LazyListState是用来同步滑动状态,下列通过enum对四个类别名称进行封装,然后通过when进行区分,最后在分别实现不同类别的数据效果

@Composable
fun MyLazyList(type: Type,listState: LazyListState = rememberLazyListState(),
) {LazyColumn(state = listState,verticalArrangement = Arrangement.spacedBy(16.dp)) {itemsIndexed(type.categories) { tabIndex, tab ->Column(modifier = Modifier.fillMaxWidth()){Text(text = tab, fontSize = 18.sp)Spacer(modifier = Modifier.height(10.dp))when (tab) {Category.Vegetables.title -> {type.vegetables.forEachIndexed { index, vegetable ->Row(horizontalArrangement = Arrangement.Center,verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().height(40.dp).background(Color.Gray)) {Text(vegetable.content,textAlign = TextAlign.Center,color = Color.White)}if (index < type.vegetables.size - 1) Spacer(modifier = Modifier.height(10.dp))}}Category.Vehicles.title -> {type.vehicles.forEachIndexed { index, vehicle ->Row(horizontalArrangement = Arrangement.Center,verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().height(40.dp).background(Color.Gray)) {Text(vehicle.content,textAlign = TextAlign.Center,color = Color.White)}if (index < type.vehicles.size - 1) Spacer(modifier = Modifier.height(10.dp))}}Category.Animals.title -> {type.animals.forEachIndexed { index, animal ->Row(horizontalArrangement = Arrangement.Center,verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().height(40.dp).background(Color.Gray)) {Text(animal.content,textAlign = TextAlign.Center,color = Color.White)}if (index < type.animals.size - 1) Spacer(modifier = Modifier.height(10.dp))}}Category.Fruits.title -> {type.fruits.forEachIndexed { index, fruit ->Row(horizontalArrangement = Arrangement.Center,verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().height(40.dp).background(Color.Gray)) {Text(fruit.content,textAlign = TextAlign.Center,color = Color.White)}if (index < type.fruits.size - 1) Spacer(modifier = Modifier.height(10.dp))}}}if (tabIndex < type.categories.size - 1) Spacer(modifier = Modifier.height(20.dp))}}}
}

如何同步实现?

我们需要一种方法来反映一个状态与另一个状态的状态,这意味着无论当前所选索引的值是多少,它都应该反映列表状态中的正确位置,反之亦然,无论列表状态的当前位置是什么,它都反映正确的索引。

监听列表滑动变化

通过LazyListState来了解列表的滑动状态,每次滑动都会都会重新计算列表滑动位置和Tab对应的滑动关系。通过查找第一个完全或部分可见的列表子项以及最后一个完全可见的列表子项来最终确定将所要选择的索引。如果发生变化,则返回变化结果,然后随即变化Tab状态

@Composable
fun lazyListTabSync(syncedIndices: List<Int>,lazyListState: LazyListState = rememberLazyListState(),tabsCount: Int? = null,smoothScroll: Boolean = true
): TabSyncState {require(syncedIndices.isNotEmpty()) {"You can't use the mediator without providing at least one index in the syncedIndices array"}if (tabsCount != null) {require(tabsCount <= syncedIndices.size) {"The tabs count is out of the bounds of the syncedIndices list provided. " +"Either add an index to syncedIndices that corresponds to an item to your lazy list, " +"or remove your excessive tab"}}var selectedTabIndex by remember { mutableStateOf(0) }LaunchedEffect(lazyListState) {snapshotFlow { lazyListState.layoutInfo }.collect {var itemPosition = lazyListState.findFirstFullyVisibleItemIndex()if (itemPosition == -1) {itemPosition = lazyListState.firstVisibleItemIndex}if (itemPosition == -1) {return@collect}if (lazyListState.findLastFullyVisibleItemIndex() == syncedIndices.last()) {itemPosition = syncedIndices.last()}if (syncedIndices.contains(itemPosition) && itemPosition != syncedIndices[selectedTabIndex]) {selectedTabIndex = syncedIndices.indexOf(itemPosition)}}}return TabSyncState(selectedTabIndex,lazyListState,rememberCoroutineScope(),syncedIndices,smoothScroll)
}

计算列表子项索引位置

由上述可知,我们一共建立了四个数据类别,则共有四个子项,每一个子项又是一个列表,此处我们计算的是单个数据类别位置,通过计算其可见子项的偏移量判断是否在对应范围内,从而返回对应的Tab子项下标

fun LazyListState.findFirstFullyVisibleItemIndex(): Int = findFullyVisibleItemIndex(reversed = false)fun LazyListState.findLastFullyVisibleItemIndex(): Int = findFullyVisibleItemIndex(reversed = true)fun LazyListState.findFullyVisibleItemIndex(reversed: Boolean): Int {layoutInfo.visibleItemsInfo.run { if (reversed) reversed() else this }.forEach { itemInfo ->val itemStartOffset = itemInfo.offsetval itemEndOffset = itemInfo.offset + itemInfo.sizeval viewportStartOffset = layoutInfo.viewportStartOffsetval viewportEndOffset = layoutInfo.viewportEndOffsetif (itemStartOffset >= viewportStartOffset && itemEndOffset <= viewportEndOffset) {return itemInfo.index //返回当前滑动列表的子项所属Tab的下标}}return -1
}

Tab滑动

下面定义了三个解构声明语句,其中其中的 component1()component2() component3() 函数是在 Kotlin 中广泛使用的约定原则。下列实现的功能为通过启动一个协程作用域让Tab对应的列表滚动到相应的位置

@Stable
class TabSyncState(var selectedTabIndex: Int,var lazyListState: LazyListState,private var coroutineScope: CoroutineScope,private var syncedIndices: List<Int>,private var smoothScroll: Boolean,
) {operator fun component1(): Int = selectedTabIndexoperator fun component2(): (Int) -> Unit = {require(it <= syncedIndices.size - 1) {"The selected tab's index is out of the bounds of the syncedIndices list provided. " +"Either add an index to syncedIndices that corresponds to an item to your lazy list, " +"or remove your excessive tab"}selectedTabIndex = itcoroutineScope.launch {if (smoothScroll) {lazyListState.animateScrollToItem(syncedIndices[selectedTabIndex])} else {lazyListState.scrollToItem(syncedIndices[selectedTabIndex])}}}operator fun component3(): LazyListState = lazyListState
}

此Demo改编至一个国外博主,其原作者Github链接地址如下所示
原作者Github链接

这篇关于Android Compose——ScrollableTabRow和LazyColumn同步滑动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

基于Redis有序集合实现滑动窗口限流的步骤

《基于Redis有序集合实现滑动窗口限流的步骤》滑动窗口算法是一种基于时间窗口的限流算法,通过动态地滑动窗口,可以动态调整限流的速率,Redis有序集合可以用来实现滑动窗口限流,本文介绍基于Redis... 滑动窗口算法是一种基于时间窗口的限流算法,它将时间划分为若干个固定大小的窗口,每个窗口内记录了该时间

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Nacos集群数据同步方式

《Nacos集群数据同步方式》文章主要介绍了Nacos集群中服务注册信息的同步机制,涉及到负责节点和非负责节点之间的数据同步过程,以及DistroProtocol协议在同步中的应用... 目录引言负责节点(发起同步)DistroProtocolDistroSyncChangeTask获取同步数据getDis

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r