[Android]Jetpack Compose 实现Banner轮播图
前言
Jetpack Compose 是推荐用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速打造生动而精彩的应用。
本次就使用 Jetpack Compose里面的分页器(pager)来实现一个常用控件——轮播图(Banner)。这里需要对Android Compose有一定的了解,可以先去学习一下Compose的基本知识,这里放上官方直达:直达官方Compose 。
同时这里也放上分页器的官方文档,可以了解更多高级功能,直达:官方分页器文档。
需求分析
首先需要分析一下实现轮播图的功能。
轮播图需要一直循环滑动播放,所以首先就是需要一个具有单页水平滑动效果的控件。在2023年3月底,Google 正式发布Jetpack Compose 的 1.4 版本,在这次的更新中,新增了 pager 等控件,它实现了View中ViewPager类似的功能。因此,水平分页器—— HorizontalPager 可以完美起这个重任。
可以分析一下HorizontalPager的源码
@Composable
@ExperimentalFoundationApi
fun HorizontalPager(
state: PagerState,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
pageSize: PageSize = PageSize.Fill,
beyondBoundsPageCount: Int = 0,
pageSpacing: Dp = 0.dp,
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
flingBehavior: SnapFlingBehavior = PagerDefaults.flingBehavior(state = state),
userScrollEnabled: Boolean = true,
reverseLayout: Boolean = false,
key: ((index: Int) -> Any)? = null,
pageNestedScrollConnection: NestedScrollConnection =
PagerDefaults.pageNestedScrollConnection(
Orientation.Horizontal
),
pageContent: @Composable PagerScope.(page: Int) -> Unit
)
其中最重要的当然是PagerState,顾名思义,这个就是用来管理HorizontalPager内pager状态的。这里是HorizontalPager的简单使用。
// 显示 10 个项目
HorizontalPager(pageCount = 10) { page ->
// 每一页的内容,比如显示个文本
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
然后就是在人没有对其进行滑动的时候,需要轮播图进行自动循环,并具有延迟展示图片内容。PagerState提供了方法可以获得当前Pager是否处于拖动状态。所以可以先使用这个方法获得当前状态,然后进行判断。延迟则可以使用delay函数来实现。
val isDragged by pagerState.interactionSource.collectIsDraggedAsState()
最后还需要一个指示器,也可以使用 Box 或者 Canvas 简单实现,方法众多,可以尽情发挥。
代码实现
通过上面的分析,可以来实现轮播图Banner了,现在将实现分为三步。
水平分页器
首先需要定义好Pager的数量——pagerCount。这里我定义了一个List用来存储图片的URL,之后也是使用了coil库来加载图片。因此,这里的 pagerCount 就等于 images.size。
接着就是定义 HorizontalPager 的 pagerState,直接使用 rememberPagerState,然后将 pagerCount 传入。
然后就可以使用HorizontalPager了,这里也设置了一下pageSpacing 来分隔此分页器中页面的空间量。同时写了个 BannerCard 来修饰一下图片,传入图片URL,然后使用 coil 的 AsyncImage 来加载图片。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Banner(
images: List<String>,
autoScrollDuration: Long = 1500L //延迟的时间,可以自行设置,后面要用到。
) {
val pagerCount = images.size
val pagerState = rememberPagerState{
pagerCount
}
Box {
HorizontalPager(
state = pagerState,
contentPadding = PaddingValues(horizontal = 32.dp),
pageSpacing = 16.dp
) {index ->
BannerCard(
imageURL = images[index],
modifier = Modifier
.padding(top = 10.dp)
)
}
}
}
@Composable
fun BannerCard(
imageURL : String,
modifier : Modifier
){
Card (modifier = modifier){
AsyncImage(model = imageURL, contentDescription = null)
}
}
实现效果如下

自动循环与延迟
首先先定义一个布尔类型的 isDragged 来获取是否处于拖动状态,然后对其进行判断,当没有人为拖动时,使用kotlin中的with 函数,在里面设立一个局部变量来储存当前页数的值,然后将其作为 LaunchedEffect (协程是Android kotlin开发中一个重要知识,在Compose中采用附带效应 effect 来实现协程开发)的参数,在协程使用 pagerState 的函数 animateScrollToPage 来实现自动滑动,nextPage 通过当前页数 + 1然后再对总页数取模来实现循环。
val isDragged by pagerState.interactionSource.collectIsDraggedAsState()
if (isDragged.not()){
with(pagerState){
var currentPageKey by remember { mutableIntStateOf(0) }
LaunchedEffect(key1 = currentPageKey){
launch {
delay(timeMillis = autoScrollDuration)
val nextPage = (currentPage + 1).mod(pageCount)
animateScrollToPage(page = nextPage)
currentPageKey = nextPage
}
}
}
}
将其加入后再次启动(虚拟机!启动!!!)如下,发现已经可以自己循环播放了。

添加指示器
添加指示器,本蒟蒻采用的是 Canvas 来实现。首先在一行中使用 repeat 函数进行重复,有多少张图就 repeat 多少次,即repeat(pageCount),然后定义颜色,与当前页数相同的指示点颜色与其他点颜色进行区别,既然要画点,所以使用 Canvas 中的 drawCircle 来进行绘画,可以自行定义大小。
因为要使用pagerState来获得当前页数,所以指示器需要传入pagerState,和pagerCount。同时需要通过 modifier 修饰符将指示器放在父 Box 容器的底部,这样指示器就可以显示在图片的上方,并处于底部。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DotIndicators(
pageCount : Int,
pagerState : PagerState,
modifier: Modifier
){
Row (
modifier = modifier.padding(bottom = 7.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Bottom
){
repeat(pageCount){iteration ->
val color = if (pagerState.currentPage == iteration) MaterialTheme.colorScheme.surface
else MaterialTheme.colorScheme.onSurface
Canvas(
modifier = Modifier
.size(12.dp)
.padding(horizontal = 2.dp), onDraw = {
drawCircle(color)
})
}
}
}
最后再次启动!

总结
如此如此,这般这般,就实现了轮播图的效果,如果任觉得不满于此,也可以跟随自己的想法去将其变得更加 niu pi。同时也可以多多探索。