Android Jetpack Compose实现轮播图效果

Android Jetpack Compose实现轮播图效果

在最近思索如何使用Compose方式改进我的开源TMDB电影列表应用程序的主屏幕时,一个激动人心的概念浮现在我的脑海中——为什么不整合一个吸引人的轮播图来展示即将上映的电影呢?在本文中,我将分享我的开发和实现自定义轮播图的经历,提供涉及不同步骤的见解。

首先,我搜索了现有的解决方案,考虑到Material Design 3文档中有一个轮播图组件的可用性。然而,我很快发现它尚未发布用于Jetpack Compose,而且仅在最新的MDC-Android(视图系统)Alpha版本中可访问。这迫使我决定开发自己的轮播图,利用HorizontalPager,这是一个Compose组件,允许水平滚动和页面变换。

为了有效地解决开发过程,我将问题分为四个不同的部分:

  1. HorizontalPager转换为实现所需的旋转木马效果。
  2. 加入指示器显示当前可见页面。
  3. HorizontalPager页面实现自动滚动。
  4. 利用动画增强即将出现的页面的外观,同时聚焦当前显示的页面并淡化其他页面。
    让我们逐个详细讨论这些部分。

HorizontalPager转换为实现所需的旋转木马效果

为了创建所需的旋转木马效果,我们必须首先自定义HorizontalPager以部分显示前一个和后一个页面。
为此,HorizontalPager提供了两个参数:

  1. contentPadding:允许我们从每个轴应用边距,创建所需的页面周围间距。
  2. pageSpacing:在两个相邻页面之间引入间隙,确保视觉上吸引人的布局。

Android Jetpack Compose实现轮播图效果_第1张图片

HorizontalPager(
    contentPadding = PaddingValues(horizontal = 32.dp),
    pageSpacing = 16.dp
) { page ->
    ...
    CarouselItem(item)
}

将指示器合并到页面中以显示当前可见页面

这个任务很容易完成。我只需要将HorizontalPager置于Box布局中,并将DotIndicators合成到Box底部对齐即可。

Box {
    HorizontalPager(pageCount = pageCount, state = pagerState) { page ->
        CarouselItem(itemList[page])
    }

    DotIndicators(
        pageCount = pageCount,
        pagerState = pagerState,
        modifier = Modifier.align(Alignment.BottomCenter)
    )
}

我们创建了一个名为DotIndicators的可组合组件。它需要两个参数:页数(pageCount)和页面状态(pageState)。我们使用页数(pageCount)添加相同数量的指示器,而使用页面状态(pageState)使选定的点显示更暗。内部实现如下:

@Composable
fun DotIndicators(
    ...,
    modifier: Modifier
) {
    Row(modifier = modifier) {
        repeat(pageCount) { iteration ->
            val color = if (pagerState.currentPage == iteration) selectedColor else unselectedColor
            Box(
                modifier = Modifier
                    .clip(CircleShape)
                    .background(color)
            )
        }
    }
}

上面指示器效果如下:
Android Jetpack Compose实现轮播图效果_第2张图片

为HorizontalPager页面实现自动滚动

目前,我们的设置要求用户手动水平滑动以导航到下一页。为了增强用户体验,我们可以实现自动滚动。我们通过利用HorizontalPager提供的Coroutine、LaunchedEffect和PagerState来实现这一点。
以下是一个完全具备功能的示例实现:

@Composable
fun AwesomeCarousel(
    pageCount: Int = 10,
    pagerState: PagerState = rememberPagerState(),
    autoScrollDuration: Long = 3000L
) {
    val isDragged by pagerState.interactionSource.collectIsDraggedAsState()
    if (isDragged.not()) {
        with(pagerState) {
            var currentPageKey by remember { mutableStateOf(0) }
            LaunchedEffect(key1 = currentPageKey) {
                launch {
                    delay(timeMillis = autoScrollDuration)
                    val nextPage = (currentPage + 1).mod(pageCount)
                    animateScrollToPage(page = nextPage)
                    currentPageKey = nextPage
                }
            }
        }
    }

    HorizontalPager(pageCount = pageCount, state = pagerState) { page ->
        CarouselItem(itemList[page])
    }
}

代码片段中的主要要点是:

  1. LaunchedEffectLaunchedEffect 是 Jetpack Compose 提供的一种特殊效果,允许我们在协程中执行副作用,当特定键值发生改变时触发。在这种情况下,key1 参数设置为 currentPageKey,这意味着只要 currentPageKey 的值发生更改,该效果就会被触发。在LaunchedEffect块内,我们使用 launch 函数启动一个新的协程。这个协程在合成过程中异步执行并且独立于合成进程。

  2. delay(timeMillis = autoScrollDuration):这一行引入了一个延迟,协程会在 autoScrollDuration 变量指定的持续时间内暂停,然后继续执行下一行代码。在暂停后,我们再次更改键,这导致协程重新启动,因此这意味着 LaunchedEffect 内部的块将无限次地执行。

  3. isDragged.not():此代码段确保仅在不拖动页面时执行后续操作。它控制自动滚动行为,避免了用户交互和自动滚动过程之间的冲突。例如,如果用户正在滑动页面,自动滚动将停止。

利用动画增强页面外观

实现的最后一部分着重于增强轮播页面的视觉过渡效果。通过利用graphicLayer修饰器,我们可以实现平滑无缝的过渡效果。关键动画包括在淡出旧项的同时淡入新项,以及在项接近或远离中心时进行缩放。

以下代码计算当前页面和目标页面之间的偏移量,然后使用此偏移量在0.7和1之间插值出一个转换值。然后将此转换值应用于graphicLayeralphascaleY属性。

fun Modifier.carouselTransition(page: Int, pagerState: PagerState) =
    graphicsLayer {
        val pageOffset =
            ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue

        val transformation =
            lerp(
                start = 0.7f,
                stop = 1f,
                fraction = 1f - pageOffset.coerceIn(0f, 1f)
            )
        alpha = transformation
        scaleY = transformation
    }
  1. 我们计算了在页面分页器中当前页面和目标页面之间的偏移量。它考虑了整个页面差异(pagerState.currentPage-page)和分数(pagerState.currentPageOffsetFraction),后者表示在页面之间转换的进度。绝对值确保偏移量始终为正数。
  2. lerp函数用于基于分数在起始点和结束点之间插值计算一个值。在这种情况下,起始值为0.7f,停止值为1f,而分数则被计算为1f - pageOffset.coerceIn(0f, 1f)。coerceIn函数可以确保分数保持在0到1的范围内。
  3. alpha = transformationtransformation的值分配给了graphicLayeralpha属性。将alpha设置为介于0和1之间的值可以确定图层的透明度。值为1表示图层完全不透明,而值为0表示它完全透明。
  4. scaleY = transformation同样,这行将transformation的值分配给了graphicLayerscaleY属性。调整scaleY属性会垂直缩放图层。值为1表示图层的原始大小,而小于1的值会减少垂直比例,大于1的值会增加垂直比例。
    我们在HorizontalPageritem modifier上利用graphicLayer扩展函数。这使我们可以对每个旋转木马项目应用变换并创建效果。
HorizontalPager() { page ->
    val page = list[page]
    CarouselItem(
        item = page,
        modifier = Modifier.carouselTransition(page, pagerState)
    )
}

github地址

https://github.com/TheSomeshKumar/Flixplorer

你可能感兴趣的:(jetpack,compose,android,jetpack,android,动画)