Compose 中的呼叫器

如要以左右或上下方式翻閱內容,可以分別使用 HorizontalPagerVerticalPager 可組合函式。這些可組合項的功能與 View 系統中的 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 參數傳遞至分頁器。您可以在這個狀態下呼叫 PagerState#scrollToPage(),方法是在 CoroutineScope 內:

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

分頁器,內容下方顯示圓圈指標
圖 3. 內容下方顯示圓圈指標的分頁器

對內容套用項目捲動效果

常見用途是使用捲動位置,將效果套用至分頁檢視器項目。如要瞭解某個頁面與目前所選頁面的距離,可以使用 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 邊框間距會將頁面靠向結尾對齊:

分頁器,開頭有邊框間距,顯示的內容會向結尾對齊

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 會將輕拂手勢可捲動的頁面數上限設為一次一頁。如要變更這項設定,請在 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 會每兩秒自動前進分頁器。
  • 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 可組合函式會以水平方式排列頁面指標。
  • 自訂頁面指標會顯示為一列圓圈,其中每個 Box 都會裁剪為 circle,代表一個頁面。
  • 目前頁面的圓圈會以 DarkGray 著色,其他圓圈則為 LightGraycurrentPageIndex 參數會決定要以深灰色顯示哪個圓圈。

結果

這部影片會顯示上述程式碼片段的基本自動前進分頁器:

圖 1. 自動換頁的換頁器,每頁之間有兩秒的延遲。

其他資源