用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)
      )
    }
  }
}
相关推荐
_李小白13 小时前
【Android FrameWork】第二十天:AudioTrack
android·gitee
走在路上的菜鸟13 小时前
Android学Dart学习笔记第十节 循环
android·笔记·学习·flutter
LiuYaoheng13 小时前
【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil
android·java
春卷同学14 小时前
打砖块 - Electron for 鸿蒙PC项目实战案例
android·electron·harmonyos
wei1155614 小时前
compose自定义控件
android
m0_6324825014 小时前
Android端测试类型、用例设计、测试工具(不涉及自动化测试)
android
走在路上的菜鸟15 小时前
Android学Dart学习笔记第九节 Patterns
android·笔记·学习·flutter
AllBlue15 小时前
unity导出成安卓工程,集成到安卓显示
android·unity·游戏引擎
QQ_43766431416 小时前
常见题目及答案
android·java·开发语言
菜鸟小九16 小时前
mysql运维(主从复制)
android·运维·mysql