Jetpack Compose:使用PagerIndicator和Infinity实现滚动的HorizontalPager

news/2025/1/13 2:31:04/

Jetpack Compose:使用PagerIndicator和Infinity实现滚动的HorizontalPager

Android | Kotlin | Jetpack Compose | ViewPager | PagerIndicator
可能你已经知道,Jetpack Compose 默认不包含内置的ViewPager组件。然而,我们可以通过在 build.gradle 文件中添加 accompanist 库依赖,将 ViewPager 功能集成到我们的项目中。

implementation "com.google.accompanist:accompanist-pager:0.28.0"

为了将指示器纳入其中,我们还将利用accompanist库。

implementation "com.google.accompanist:accompanist-pager-indicators:0.28.0"

注意:对于此项目,我们使用的是Compose版本1.3.1和Kotlin版本1.8.10。
在这里插入图片描述

让我们从创建一个HorizontalPager 开始

accompanist库的存在,创建HorizontalPager是一项简单的任务。

HorizontalPager(count = pageCount,state = pagerState,modifier = modifier
) {// page content
}
  • Count:页面数
    我们将计数设置为非常大的数,如Int.MAX_VALUE,这样我们就可以实现无限滚动行为。
// Used Int.MAX_VALUE for infinity scroll
val pageCount = Int.MAX_VALUE
  • State:用于控制或观察分页器状态的状态对象。
    对于状态创建,我们只需要 initialPage 值。为了实现双向无限滚动,我们应该从给定页面计数的中间开始。因此,initialPage 可以设置如下示例:
val middlePage = pageCount / 2
val pagerState = rememberPagerState(initialPage = middlePage)

尽管一开始所有东西似乎都是正确的,但我们很快就会遇到一个涉及最初显示页面的问题。

技巧

为了实现ViewPager的无限行为,我们通过设置计数为一个非常大的数字来实现了一种解决方案。但是,我们实际的物品列表(奖杯)要小得多。为了确保ViewPager显示我们真正列表中的正确页面而不创建重复页面,我们需要适当地处理页面编号。

为了解决这个挑战,我们将提供的页面编号除以奖杯列表的大小。这个除法允许我们在我们的真实列表中获取正确的页面索引。通过执行这个计算,我们确保ViewPager只显示列表中的实际物品,防止任何重复。

通过利用这种方法,我们可以在ViewPager中轻松导航通过奖杯列表,同时保持其无限行为。

看下面的例子:

val realSize = trophies.sizeHorizontalPager(count = pageCount,state = pagerState,modifier = modifier
) { page -> val realPage = page % realSize// max value is trophies.sizeTrophyWidget(realPage, trophy = trophies[realPage])
}

你懂了吗?不懂?(那我们就来算一下吧!)
page count

看起来我们的数学运算正常运行!

如果您仔细观察,就会发现初始页面不是奖杯列表中的第一个。实际上,初始状态取决于奖杯列表的大小。为了解决这个差异并确保正确的初始状态,有必要计算并传递一个参数到ViewPager状态。

val realSize = trophies.sizeval middlePage = pageCount / 2
// Init the PagerState with a very large number and make it always start from the first item of the real list
val pagerState = rememberPagerState(initialPage = middlePage - (middlePage % realSize))

通过将middlePage减去middlePage与奖杯数量取余的结果,确保ViewPager将从奖杯列表的开头开始。

页面指示器

添加指示器也很简单,我们只需要添加HorizontalPagerIndicator并将pagerState作为参数传递即可。

Android | HorizontalPager | Indicator
然而,这里存在一个问题!如果您尝试在不指定列表(奖杯)的实际大小的情况下使用pagerState,则应用程序将会空白页。那是因为HorizontalPagerIndicator的默认pageCount设置为PagerState.pageCount的值,而在我们的情况下,这是一个非常大的数。

幸运的是,我们可以通过将pageCount作为参数添加到HorizontalPagerIndicator中来指定pageCount

看一个例子:

HorizontalPagerIndicator(pagerState = pagerState,pageCount = realSize,pageIndexMapping = { it % realSize },activeColor = Color.White,modifier = modifier.align(Alignment.BottomCenter).padding(bottom = 12.dp)
)

我们还必须描述如何通过将页面传递给pageIndexMapping函数来获取活动指示器的位置。这可以通过将pagerState.currentPage除以奖杯列表的大小来实现。

如上例所示,您可以实现以下代码段:

pageIndexMapping = { currentPage % realSize }

要获取活动指示器的位置,您可以使用pageIndexMapping函数,并使用pagerState.currentPage和奖杯列表的大小执行模运算。

自动滚动

如果您还需要您的页面自动滚动,可以使用以下代码片段:

// Start auto-scroll effect
LaunchedEffect(isDraggedState) {// convert compose state into flowsnapshotFlow { isDraggedState.value }.collectLatest { isDragged ->// if not isDragged start slide animationif (!isDragged) {// infinity loopwhile (true) {// duration before each scroll animationdelay(5_000L)runCatching {pagerState.animateScrollToPage(pagerState.currentPage.inc() % pagerState.pageCount)}}}}
}

完整代码如下:

private const val SCROLL_ANIMATION_DURATION = 5_000L@OptIn(ExperimentalPagerApi::class)
@Composable
fun InfinityHorizontalPager(modifier: Modifier = Modifier) {Column(verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Box(modifier = modifier.fillMaxWidth().height(400.dp)) {// Used Int.MAX_VALUE for infinity scrollval pageCount = Int.MAX_VALUE// The actual view pager size (for the HorizontalPagerIndicator)val realSize = trophies.size// Start from the middle in order to the infinity scroll for both sidesval middlePage = pageCount / 2// Init the PagerState with a very large number and make it always start from the first item of the real listval pagerState = rememberPagerState(initialPage = middlePage - (middlePage % realSize))val isDraggedState = pagerState.interactionSource.collectIsDraggedAsState()HorizontalPager(count = pageCount,state = pagerState,modifier = modifier.fillMaxWidth().fillMaxHeight().background(MaterialTheme.colors.background),) {val page = it % realSize// max value is trophies.sizeTrophyWidget(page, trophy = trophies[page])}Surface(modifier = Modifier.padding(bottom = 8.dp).align(Alignment.BottomCenter),shape = CircleShape,color = Color.Black.copy(alpha = 0.5f)) {HorizontalPagerIndicator(pagerState = pagerState,pageCount = realSize,pageIndexMapping = { it % realSize },activeColor = Color.White,modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp))}// Start auto-scroll effectLaunchedEffect(isDraggedState) {// convert compose state into flowsnapshotFlow { isDraggedState.value }.collectLatest { isDragged ->// if not isDragged start slide animationif (!isDragged) {// infinity loopwhile (true) {// duration before each scroll animationdelay(SCROLL_ANIMATION_DURATION)runCatching {pagerState.animateScrollToPage(pagerState.currentPage.inc() % pagerState.pageCount)}}}}}}}
}@Composable
fun TrophyWidget(page: Int,trophy: TrophyCard,modifier: Modifier = Modifier
) {Box(modifier = modifier.padding(horizontal = 16.dp).fillMaxWidth().fillMaxHeight().background(Color.Black).clip(shape = RoundedCornerShape(size = 12.dp)),) {AsyncImage(model = ImageRequest.Builder(LocalContext.current).data(trophy.image).crossfade(true).build(),modifier = modifier.fillMaxWidth().fillMaxHeight().clip(shape = RoundedCornerShape(size = 12.dp)),contentDescription = null,contentScale = ContentScale.FillBounds)Column(modifier = Modifier.fillMaxWidth().background(color = Color.Black.copy(alpha = 0.5f)).padding(10.dp).align(Alignment.BottomStart)) {Text(text = trophy.location,color = Color.White,style = Typography.h6,textAlign = TextAlign.Center)Text(text = trophy.year,color = Color.White,style = Typography.h4,textAlign = TextAlign.Center)}Text(text = "$page",style = Typography.body1,color = Color.Black,textAlign = TextAlign.Center,modifier = Modifier.padding(10.dp).clip(shape = RoundedCornerShape(size = 4.dp)).background(Color.White).padding(10.dp).align(Alignment.BottomEnd))}
}@Preview(showBackground = true, showSystemUi = true)
@Composable
fun DefaultPreview() {InfinityPagerTheme {Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colors.background) {InfinityHorizontalPager()}}
}

在这里插入图片描述


http://www.ppmy.cn/news/293638.html

相关文章

【JavaEE】网络编程之TCP套接字

目录 1、TCP套接字 1.1、ServerSocket 常用API 1.2、Socket 常用API 2、基于TCP套接字实现一个TCP回显服务器 2.1、服务器端代码 2.2、客户端代码 2.3、解决服务器不能同时和多个客户端建立链接的问题 3、基于TCP socket 写一个简单的单词翻译服务器 1、TCP套接字 T…

浅谈 UUID 生成原理及优缺点

UUID 是一套用于生成全局唯一标识符的标准,也被称为 GUID (Globally Unique Identifier),通过使用 UUID 可以在分布式系统中生成唯一的 ID。UUID 的生成方式有多种,本文将详细讲解 UUID 的生成原理、特性、实用场景以及…

Android壳程序实现方式对比

vs. 原生开发、纯H5(依赖浏览器)、浏览器壳程序(混合应用的方式) 本篇指的是最后一种 Android手机壳程序,用系统WebView 和 内嵌Chromium 实现方式的对比使用系统WebView内嵌ChromiumFirefox的引擎 - GeckoView 组件说…

联发科MT76x8使用1-芯片对比

上面是我创建的群聊,欢迎新朋友的加入。 最近新到手一个MT-76X8的板子,盖了个铁壳壳,丝印上写的是MT-7628. 学习一下。 特意对比了MT-7628和MT-7688 对比了一下,没什么太大区别 官方资源路径 https://docs.labs.mediatek.com…

MT7688详细芯片资料下载  MT7688规格说明简介

MT7688详细芯片资料下载 MT7688规格说明简介 今天给大家分享MT7688芯片资料的相关问题,具体资料到闯客网技术论坛下载,也可以加群解决:813238832 或者资料链接:https://bbs.usoftchina.com/ Q: 联发科 MT7688AN是什么? ? 联发…

MT7687芯片资料MT7687原理图资料

MT7687是一种高度集成的单片芯片,提供应用程序处理器、低功率1t1r 802.11b/g/n wi-fi、子系统和电源管理单元。应用程序处理器子系统包含一个带有浮点单元的臂Cortex-m4。它还支持一系列接口,包括uart,i2c,spi,i2s&…

摩托罗拉MT788刷机

好久不用的MT788今天拿出来一看,竟然忘记手势密码,oh my god 然后尝试进入recovery 模式刷机 分享一下方法:1、将手机处于关机状态下。2、按住音量下键和音量上键不松,再同时按电源键开机等待几秒钟后屏幕黑了就松开所有的按键后会…

AM1808 OMAPL138 研发

说起AM1808,是去年年底画的板子,但是出了点问题,一直扔下了。 首先说说AM1808 的特性吧,感觉比较有用的就是: 1、16bitDDR2 接口支持到512M, 2、一个16bit EMIF 接口,也就是接nor和nand的接口&a…