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 DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Mac备忘录怎么导出/备份和云同步? Mac备忘录使用技巧

《Mac备忘录怎么导出/备份和云同步?Mac备忘录使用技巧》备忘录作为iOS里简单而又不可或缺的一个系统应用,上手容易,可以满足我们日常生活中各种记录的需求,今天我们就来看看Mac备忘录的导出、... 「备忘录」是 MAC 上的一款常用应用,它可以帮助我们捕捉灵感、记录待办事项或保存重要信息。为了便于在不同

查看MySql主从同步的偏移量方式

《查看MySql主从同步的偏移量方式》:本文主要介绍查看MySql主从同步的偏移量方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 1.mysql的主从同步方案mysqlphp为了在实现读写分离,主库写,从库读mysql的同步方案主要是通过从库读取主库的binl

Kotlin Compose Button 实现长按监听并实现动画效果(完整代码)

《KotlinComposeButton实现长按监听并实现动画效果(完整代码)》想要实现长按按钮开始录音,松开发送的功能,因此为了实现这些功能就需要自己写一个Button来解决问题,下面小编给大... 目录Button 实现原理1. Surface 的作用(关键)2. InteractionSource3.

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四