Compose 中的分页器

如需向左或向右或向上或向下翻阅内容,您可以分别使用 HorizontalPagerVerticalPager 可组合项。这些可组合项的功能与 View 系统中的 ViewPager 类似。默认情况下,HorizontalPager 占据整个屏幕的宽度,VerticalPager 占据整个屏幕的高度,并且分页器一次只能快速滑动一页。这些默认值均可配置。

HorizontalPager

如需创建可向左和向右水平滚动的页面浏览器,请使用 HorizontalPager

图 1HorizontalPager 演示

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

VerticalPager

如需创建上下滚动的页面浏览器,请使用 VerticalPager

图 2VerticalPager 演示

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
VerticalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

延迟创建

HorizontalPagerVerticalPager 中的页面会在需要时延迟组合和布局。当用户滚动浏览页面时,可组合项会移除不再需要的所有页面。

在屏幕外加载更多页面

默认情况下,分页器只会加载屏幕上可见的页面。如需在屏幕外加载更多页面,请将 beyondBoundsPageCount 设置为大于零的值。

滚动到分页器中的某个项

如需滚动到分页器中的某个页面,请使用 rememberPagerState() 创建 PagerState 对象,并将其作为 state 参数传递给分页器。您可以在 CoroutineScope 内对此状态调用 PagerState#scrollToPage()

val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.scrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

如果您想为页面添加动画效果,请使用 PagerState#animateScrollToPage() 函数:

val pagerState = rememberPagerState(pageCount = {
    10
})

HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.animateScrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

接收有关页面状态更改的通知

PagerState 有三个包含页面相关信息的属性:currentPagesettledPagetargetPage

  • currentPage:与贴靠位置最近的页面。默认情况下,贴靠位置位于布局的开头。
  • settledPage:未运行任何动画或滚动时显示的页码。这与 currentPage 属性不同,因为如果网页足够接近固定位置,currentPage 会立即更新,但 settledPage 会保持不变,直到所有动画都运行完毕。
  • targetPage:滚动动作的建议停止位置。

您可以使用 snapshotFlow 函数来观察这些变量的更改并对其做出响应。例如,如需在每次网页更改时发送 Google Analytics 事件,您可以执行以下操作:

val pagerState = rememberPagerState(pageCount = {
    10
})

LaunchedEffect(pagerState) {
    // Collect from the a snapshotFlow reading the currentPage
    snapshotFlow { pagerState.currentPage }.collect { page ->
        // Do something with each page change, for example:
        // viewModel.sendPageSelectedEvent(page)
        Log.d("Page change", "Page changed to $page")
    }
}

VerticalPager(
    state = pagerState,
) { page ->
    Text(text = "Page: $page")
}

添加页面指示器

如需向网页添加指示器,请使用 PagerState 对象获取有关从网页数量中选择了哪个网页的信息,然后绘制自定义指示器。

例如,如果您想要一个简单的圆形指示器,可以使用 pagerState.currentPage 重复圆形的数量,并根据页面是否处于选中状态更改圆形颜色:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize()
) { page ->
    // Our page content
    Text(
        text = "Page: $page",
    )
}
Row(
    Modifier
        .wrapContentHeight()
        .fillMaxWidth()
        .align(Alignment.BottomCenter)
        .padding(bottom = 8.dp),
    horizontalArrangement = Arrangement.Center
) {
    repeat(pagerState.pageCount) { iteration ->
        val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray
        Box(
            modifier = Modifier
                .padding(2.dp)
                .clip(CircleShape)
                .background(color)
                .size(16.dp)
        )
    }
}

在内容下方显示圆形指示器的页面浏览器
图 3. 在内容下方显示圆圈指示器的页面浏览器

对内容应用项滚动效果

一个常见的用例是使用滚动位置对页面浏览器项应用效果。如需了解某个网页距离当前所选网页的距离,您可以使用 PagerState.currentPageOffsetFraction。然后,您可以根据与所选网页的距离,对内容应用转换效果。

图 4。对分页器内容应用转换

例如,如需根据项与中心的距离调整项的不透明度,请对分页器中的项使用 Modifier.graphicsLayer 更改 alpha

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(state = pagerState) { page ->
    Card(
        Modifier
            .size(200.dp)
            .graphicsLayer {
                // Calculate the absolute offset for the current page from the
                // scroll position. We use the absolute value which allows us to mirror
                // any effects for both directions
                val pageOffset = (
                    (pagerState.currentPage - page) + pagerState
                        .currentPageOffsetFraction
                    ).absoluteValue

                // We animate the alpha, between 50% and 100%
                alpha = lerp(
                    start = 0.5f,
                    stop = 1f,
                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
                )
            }
    ) {
        // Card content
    }
}

自定义页面大小

默认情况下,HorizontalPagerVerticalPager 分别占据整个宽度或整个高度。您可以将 pageSize 变量设置为使用 FixedFill(默认)或自定义大小计算。

例如,如需设置固定宽度的 100.dp 页面,请执行以下操作:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    pageSize = PageSize.Fixed(100.dp)
) { page ->
    // page content
}

如需根据视口大小调整页面大小,请使用自定义页面大小计算方式。创建一个自定义 PageSize 对象,并将 availableSpace 除以 3,同时考虑项之间的间距:

private val threePagesPerViewport = object : PageSize {
    override fun Density.calculateMainAxisPageSize(
        availableSpace: Int,
        pageSpacing: Int
    ): Int {
        return (availableSpace - 2 * pageSpacing) / 3
    }
}

内容内边距

HorizontalPagerVerticalPager 都支持更改内容内边距,以便您影响页面的最大尺寸和对齐方式。

例如,设置 start 内边距可将页面对齐到末尾:

带有起始内边距的页面浏览器,显示的内容对齐于底部

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(start = 64.dp),
) { page ->
    // page content
}

startend 内边距设置为相同的值可将项水平居中:

带有开始和结束内边距的页面浏览器,用于居中显示内容

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(horizontal = 32.dp),
) { page ->
    // page content
}

设置 end 内边距可将页面对齐到起始位置:

带有开始和结束内边距的页面浏览器,显示与开始对齐的内容

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(end = 64.dp),
) { page ->
    // page content
}

您可以设置 topbottom 值,以便为 VerticalPager 实现类似的效果。值 32.dp 在这里仅用作示例;您可以将每个内边距维度设置为任何值。

自定义滚动行为

默认的 HorizontalPagerVerticalPager 可组合项指定滚动手势如何与页面浏览器配合使用。不过,您可以自定义和更改默认值,例如 pagerSnapDistanceflingBehavior

贴靠距离

默认情况下,HorizontalPagerVerticalPager 会将一次滑动手势可滚过的页面数量上限设置为 1 页。如需更改此设置,请在 flingBehavior 上设置 pagerSnapDistance

val pagerState = rememberPagerState(pageCount = { 10 })

val fling = PagerDefaults.flingBehavior(
    state = pagerState,
    pagerSnapDistance = PagerSnapDistance.atMost(10)
)

Column(modifier = Modifier.fillMaxSize()) {
    HorizontalPager(
        state = pagerState,
        pageSize = PageSize.Fixed(200.dp),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}

创建自动滚动分页器

本部分介绍了如何在 Compose 中创建带有页面指示器的自动滚动页面浏览器。项集会自动水平滚动,但用户也可以在项之间手动滑动。如果用户与分页器互动,则会停止自动滚动。

基本示例

以下代码段结合使用可创建一个带有视觉指示器的基本自动滚动分页器实现,其中每个页面都会以不同的颜色呈现:

@Composable
fun AutoAdvancePager(pageItems: List<Color>, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        val pagerState = rememberPagerState(pageCount = { pageItems.size })
        val pagerIsDragged by pagerState.interactionSource.collectIsDraggedAsState()

        val pageInteractionSource = remember { MutableInteractionSource() }
        val pageIsPressed by pageInteractionSource.collectIsPressedAsState()

        // Stop auto-advancing when pager is dragged or one of the pages is pressed
        val autoAdvance = !pagerIsDragged && !pageIsPressed

        if (autoAdvance) {
            LaunchedEffect(pagerState, pageInteractionSource) {
                while (true) {
                    delay(2000)
                    val nextPage = (pagerState.currentPage + 1) % pageItems.size
                    pagerState.animateScrollToPage(nextPage)
                }
            }
        }

        HorizontalPager(
            state = pagerState
        ) { page ->
            Text(
                text = "Page: $page",
                textAlign = TextAlign.Center,
                modifier = modifier
                    .fillMaxSize()
                    .background(pageItems[page])
                    .clickable(
                        interactionSource = pageInteractionSource,
                        indication = LocalIndication.current
                    ) {
                        // Handle page click
                    }
                    .wrapContentSize(align = Alignment.Center)
            )
        }

        PagerIndicator(pageItems.size, pagerState.currentPage)
    }
}

代码要点

  • AutoAdvancePager 函数会创建一个可自动滚动的横向分页视图。它接受 Color 对象的列表作为输入,这些对象将用作每个页面的背景颜色。
  • pagerState 是使用 rememberPagerState 创建的,该对象用于存储页面浏览器的状态。
  • pagerIsDraggedpageIsPressed 用于跟踪用户互动。
  • 除非用户拖动分页器或按下某个页面,否则 LaunchedEffect 会每 2 秒自动向前跳转一页。
  • HorizontalPager 会显示页面列表,每个页面都有一个用于显示页码的 Text 可组合项。该修饰符会填充页面,从 pageItems 设置背景颜色,并使页面可点击。

@Composable
fun PagerIndicator(pageCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        Row(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.BottomCenter)
                .padding(bottom = 8.dp),
            horizontalArrangement = Arrangement.Center
        ) {
            repeat(pageCount) { iteration ->
                val color = if (currentPageIndex == iteration) Color.DarkGray else Color.LightGray
                Box(
                    modifier = modifier
                        .padding(2.dp)
                        .clip(CircleShape)
                        .background(color)
                        .size(16.dp)
                )
            }
        }
    }
}

代码要点

  • Box 可组合项用作根元素。
    • Box 内,一个 Row 可组合项会水平排列页面指示器。
  • 自定义页面指示器显示为一排圆圈,其中每个剪裁为 circleBox 代表一页。
  • 当前网页的圆圈颜色为 DarkGray,而其他圆圈的颜色为 LightGraycurrentPageIndex 参数决定哪个圆圈以深灰色渲染。

结果

此视频展示了前面代码段中的基本自动滚动分页器:

图 1. 自动滚动分页器,每次翻页之间有 2 秒的延迟时间。