用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)
      )
    }
  }
}
相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android