用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)
      )
    }
  }
}
相关推荐
JMchen1237 小时前
Android UDP编程:实现高效实时通信的全面指南
android·经验分享·网络协议·udp·kotlin
黄林晴8 小时前
Android 17 再曝猛料:通知栏和快捷设置终于分家了,这操作等了十年
android
有位神秘人8 小时前
Android获取设备中本地音频
android·音视频
JMchen1238 小时前
Android网络安全实战:从HTTPS到双向认证
android·经验分享·网络协议·安全·web安全·https·kotlin
CS创新实验室8 小时前
Pandas 3 的新功能
android·ide·pandas
ujainu9 小时前
护眼又美观:Flutter + OpenHarmony 鸿蒙记事本一键切换夜间模式(四)
android·flutter·harmonyos
三少爷的鞋9 小时前
为什么我不在 Android ViewModel 中直接处理异常?
android
草莓熊Lotso10 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
恋猫de小郭10 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
工程师老罗16 小时前
如何在Android工程中配置NDK版本
android