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️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

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