用Compose实现一个Banner轮播组件

在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)
      )
    }
  }
}
相关推荐
weiggle13 小时前
第七篇:状态提升与单向数据流——架构设计的核心
android
xingpanvip13 小时前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
goldenrolan13 小时前
A公司物料替代测试系统 v1.7:从需求到 exe/apk 的 AI 辅助全链路实践
android·自动化测试·软件测试·python·ai
AC赳赳老秦14 小时前
用 OpenClaw 搭建服务器故障应急响应系统,自动处理 80% 常见运维故障
android·运维·服务器·python·rxjava·deepseek·openclaw
骇客之技术16 小时前
AutoLua:在安卓上写 Lua 脚本
android·junit·lua
kiros_wang17 小时前
Android 常见面试题
android
货拉拉技术17 小时前
Hook植入日志协助定位问题方案
android
FlightYe17 小时前
Android投屏MirrorCast全链路
android
Ehtan_Zheng17 小时前
Kotlin const val vs val:字节码、性能与隐藏陷阱详解
android·kotlin
墨狂之逸才17 小时前
Android TV 垃圾应用清理指南
android