Compose 中的呼叫器

如要以左右或上下的方式翻閱內容,可以分別使用 HorizontalPagerVerticalPager 可組合項。這些可組合項與檢視系統中 ViewPager 的函式類似。根據預設,HorizontalPager 會佔滿螢幕的全寬度,VerticalPager 則會佔滿整個高度,而頁面器一次只會翻頁。這些預設值皆可設定。

HorizontalPager

如要建立可以水平左右捲動的頁面器,請使用 HorizontalPager

圖 1. HorizontalPager 示範

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

VerticalPager

如要建立會向上及向下捲動的頁面器,請使用 VerticalPager

圖 2. VerticalPager 示範

// 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 函式來觀察這些變數的變更,並做出回應。舉例來說,如要在每次頁面變更時傳送分析事件,您可以執行下列操作:

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)
        )
    }
}

在內容下方顯示圓形指標的 Pager
圖 3. 在內容下方顯示圓形指標的 Pager

將項目捲動效果套用至內容

常見用途是使用捲動位置,將效果套用至頁面器項目。如要瞭解頁面與目前所選頁面之間的距離,可以使用 PagerState.currentPageOffsetFraction。接著,您就能根據與所選頁面的距離,將轉換效果套用至內容。

圖 4. 將轉換作業套用至 Pager 內容

舉例來說,如要根據項目與中心的距離調整項目的不透明度,請在頁面器內的項目上使用 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 除以三,同時將項目之間的間距納入考量:

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

內容間距

HorizontalPagerVerticalPager 皆支援變更內容邊框間距,這可讓您影響頁面的大小上限和對齊方式。

舉例來說,設定 start 邊框間距可讓頁面對齊至末端:

含有起始邊框間距的 Pager 顯示內容對齊底部

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

startend 邊框間距設為相同的值,即可將項目水平置中:

以起始和結尾邊框間距顯示內容置中的 Pager

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

設定 end 邊框間距可讓頁面對齊開頭:

以起始和結尾邊框間距顯示內容與開頭對齊的 Pager

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

您可以設定 topbottom 值,讓 VerticalPager 達到類似的效果。32.dp 值僅做為範例使用,您可將每個邊框間距維度設為任何值。

自訂捲動行為

預設的 HorizontalPagerVerticalPager 可組合項會指定捲動手勢如何與頁面器搭配運作。不過,您可以自訂及變更預設值,例如 pagerSnapDistanceflingBehaviour

貼齊距離

根據預設,HorizontalPagerVerticalPager 會設定快速滑過手勢可捲動至單一頁面的數量上限。如要變更這項設定,請在 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),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}