在Android View体系中,我们想实现Banner轮播效果的话,可以使用ViewPager2来实现,ViewPager2基于RecyclerView来实现滑动效果,支持横向和纵向,非常方便。在Compose中要想实现一样的横竖Banner轮播效果,可以使用HorizontalPager和VerticalPager两个组件来实现。
HorizontalPager实现轮播切换
HorizontalPager的使用非常简单,构造一个pagerState,再根据自己的需求,创建每一页的视图,就可以达到ViewPager的效果了,相比于ViewPager设置一个Adapter,要简单很多。示例如下
kotlin
@Composable
fun <T> banner() {
val images = listOf(R.drawable.imgA,R.drawable.imgB,R.drawable.imgC)
val pagerState = rememberPagerState(pageCount = { images.size })
HorizontalPager(pagerState, modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(images[it]),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
一个简单的左右滑动切换就实现了,若想实现上下滑动切换,则只需将HorizontalPager换成VerticalPager就可以了。上面的代码以加载本地Drawable目录下的3张图片为例实现切换效果,若想加载网络图片,借助三方库,将Image或者painter替换下就可以了。默认情况下,HorizontalPager仅加载屏幕上可见的页面,如需加载更多屏幕外的网页,将beyondViewportPageCount设置为大于0的值就可以了。
实现Banner无限滑动切换
HorizontalPager并不支持无限切换效果,要想实现无限轮播,只能自己实现相关逻辑。以上面的三张图片A、B和C为例,无限轮播需要我们将图片C向右继续切换到图片A,图片A向左继续切换到图片C,以达到循环的效果,效果如下
如上图,切换到最后一张A图片时,我们需要手动将当前位置设置为1,这样显示图片A时才能够继续左右滑动。同理当切换到最左端的图片C时,需要手动将当前位置设置为3,这样显示图片C时才能够继续左右滑动。
为了不影响数据源,我们需要将HorizontalPager的大小设置的比数据大2,当位置处于0时,取数据的最后一个,当位置处于最后一个时,取数据第一个。示例如下
kotlin
@Composable
fun <T> banner() {
val images = listOf(R.drawable.imgA,R.drawable.imgB,R.drawable.imgC)
val pagerState = rememberPagerState(1, pageCount = { images.size + 2 })
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage == 0) {
pagerState.scrollToPage(pagerState.pageCount - 2)
} else if (pagerState.currentPage == pagerState.pageCount - 1) {
pagerState.scrollToPage(1)
}
}
HorizontalPager(pagerState, modifier = Modifier.fillMaxSize()) { index ->
val imageIndex = when (index) {
0 -> pagerState.pageCount - 3
pagerState.pageCount - 1 -> 0
else -> index - 1
}
Image(
painter = painterResource(images[imageIndex]),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}

优化Banner切换无过度动画效果
当我们使用LaunchedEffect来监听pagerState.currentPage的改变,并手动重置位置时会出现一个问题,由位置3的图片向右滑动切换到图片A,或者由位置1的图片A向左滑动切换到图片C时,由于我们手动重置了位置,没有加动画效果,导致屏幕立即刷新了,效果非常不好。这是因为currentPage在足够接近贴靠位置时,currentPage会立即更新。而settledPage会在动画运行完毕后进行更新,所以只需用settledPage就能解决此问题,示例如下
kotlin
LaunchedEffect(pagerState.settledPage) {
if (pagerState.currentPage == 0) {
pagerState.scrollToPage(pagerState.pageCount - 2)
} else if (pagerState.currentPage == pagerState.pageCount - 1) {
pagerState.scrollToPage(1)
}
}
设置Banner自动轮播
前面我们实现了Banner无限轮播,要实现自动轮播,只需要加个定时器,让它自动切换下一页就行了,示例如下
kotlin
@Composable
fun <T> banner() {
LaunchedEffect(Unit) {
while (true) {
delay(3000L)
pagerState.animateScrollToPage(pagerState.currentPage + 1)
}
}
}
给Banner添加指示器
需要注意的是指示器的数量是与数据源一致,需要将pagerState的索引下标转换成和数据源索引一致,即第1个位置对接数据源和指示器最后一个,最后一个位置对接数据源和指示器第1个位置,其它位置减1对接数据源和指示器实际位置,因为在最前面插入了最后一个视频,所以要减1.指示器示例如下
kotlin
@Composable
fun <T> banner() {
val images = listOf(R.drawable.imgA,R.drawable.imgB,R.drawable.imgC)
val pagerState = rememberPagerState(pageCount = { images.size + 2 })
Row(
Modifier
.wrapContentSize()
.align(Alignment.BottomCenter)
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
repeat(images.size) {
val imageIndex = when (pagerState.currentPage) {
0 -> pagerState.pageCount - 3
pagerState.pageCount - 1 -> 0
else -> pagerState.currentPage - 1
}
Box(
modifier = Modifier
.size(DpSize(8.dp, 8.dp))
.clip(CircleShape)
.background(if (imageIndex == it) Color.Red else Color.LightGray)
)
}
}
}
