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实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

通过Docker Compose部署MySQL的详细教程

《通过DockerCompose部署MySQL的详细教程》DockerCompose作为Docker官方的容器编排工具,为MySQL数据库部署带来了显著优势,下面小编就来为大家详细介绍一... 目录一、docker Compose 部署 mysql 的优势二、环境准备与基础配置2.1 项目目录结构2.2 基

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D