Pager w sekcji Utwórz

Aby przewijać treści w poziomie lub w pionie, możesz użyć elementów kompozycyjnych HorizontalPager i VerticalPager. Mają one podobne funkcje do ViewPager w systemie widoków. Domyślnie HorizontalPager zajmuje całą szerokość ekranu, a VerticalPager – całą wysokość. Przewijanie stron odbywa się tylko o jedną stronę naraz. Wszystkie te ustawienia domyślne można skonfigurować.

HorizontalPager

Aby utworzyć przewijanie w poziomie w lewo i w prawo, użyj HorizontalPager:

Rysunek 1. Prezentacja HorizontalPager

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

VerticalPager

Aby utworzyć przewijanie w pionie w górę i w dół, użyj VerticalPager:

Rysunek 2. Prezentacja VerticalPager

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

Tworzenie leniwe

Strony w HorizontalPager i VerticalPagertworzone i układane leniwie, gdy jest to wymagane. Gdy użytkownik przewija strony, element kompozycyjny usuwa wszystkie strony, które nie są już potrzebne.

Wczytywanie większej liczby stron poza ekranem

Domyślnie przewijanie wczytuje tylko widoczne strony na ekranie. Aby wczytać więcej stron poza ekranem, ustaw beyondBoundsPageCount na wartość większą niż zero.

Przewijanie do elementu w przewijaniu

Aby przewinąć do określonej strony w przewijaniu, utwórz obiekt PagerState za pomocą rememberPagerState() i przekaż go jako parametr state do przewijania. W tym stanie możesz wywołać PagerState#scrollToPage() w 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")
}

Jeśli chcesz animować przejście do strony, użyj funkcji 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")
}

Otrzymywanie powiadomień o zmianach stanu strony

PagerState ma 3 właściwości z informacjami o stronach: currentPage, settledPage i targetPage.

  • currentPage: strona najbliższa pozycji przyciągania. Domyślnie pozycja przyciągania znajduje się na początku układu.
  • settledPage: numer strony, gdy nie jest uruchomiona żadna animacja ani przewijanie. Różni się to od właściwości currentPage, ponieważ currentPage natychmiast się aktualizuje, jeśli strona jest wystarczająco blisko pozycji przyciągania, ale settledPage pozostaje taka sama, dopóki nie zostaną zakończone wszystkie animacje.
  • targetPage: proponowana pozycja zatrzymania przewijania.

Aby obserwować zmiany tych zmiennych i na nie reagować, możesz użyć funkcji snapshotFlow. Aby na przykład wysłać zdarzenie analityczne przy każdej zmianie strony, możesz wykonać te czynności:

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

Dodawanie wskaźnika strony

Aby dodać wskaźnik do strony, użyj obiektu PagerState, aby uzyskać informacje o tym, która strona jest wybrana spośród liczby stron, i narysuj wskaźnik niestandardowy.

Aby na przykład utworzyć wskaźnik kołowy, możesz powtórzyć liczbę kół i zmienić kolor koła w zależności od tego, czy strona jest wybrana, używając 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)
        )
    }
}

Stronicowanie z okrągłym wskaźnikiem pod treścią
Rysunek 3. Przewijanie z wskaźnikiem kołowym pod treścią

Stosowanie efektów przewijania elementów do treści

Częstym przypadkiem użycia jest stosowanie efektów do elementów przewijania na podstawie pozycji przewijania. Aby sprawdzić, jak daleko strona znajduje się od wybranej strony, możesz użyć PagerState.currentPageOffsetFraction. Następnie możesz zastosować efekty przekształcenia do treści na podstawie odległości od wybranej strony.

Rysunek 4. Stosowanie przekształceń do treści przewijania

Aby na przykład dostosować krycie elementów na podstawie ich odległości od środka, zmień alpha za pomocą Modifier.graphicsLayer na elemencie w pagerze:

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

Niestandardowe rozmiary stron

Domyślnie HorizontalPager i VerticalPager zajmują odpowiednio całą szerokość lub całą wysokość. Możesz ustawić zmienną pageSize na Fixed, Fill (domyślnie) lub na niestandardowe obliczenie rozmiaru.

Aby na przykład ustawić stronę o stałej szerokości 100.dp:

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

Aby określić rozmiar stron na podstawie rozmiaru widocznego obszaru, użyj niestandardowego obliczenia rozmiaru strony. Utwórz niestandardowy PageSize obiekt i podziel availableSpace przez 3, uwzględniając odstępy między elementami:

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

Odstęp od treści

HorizontalPager i VerticalPager obsługują zmianę odstępu od treści, co pozwala wpływać na maksymalny rozmiar i wyrównanie stron.

Na przykład ustawienie odstępu start wyrównuje strony do końca:

Stronicowanie z wypełnieniem początkowym, w którym treść jest wyrównana do końca
Rysunek 5. Przewijanie z odstępem początkowym.

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

Ustawienie odstępu start i end na tę samą wartość powoduje wyśrodkowanie elementu w poziomie:

Stronicowanie z marginesami na początku i na końcu, z wyśrodkowaną zawartością
Rysunek 6. Przewijanie z odstępem poziomym.

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

Ustawienie odstępu end wyrównuje strony do początku:

Stronicowanie z marginesami na początku i na końcu, w którym treść jest wyrównana do początku
Rysunek 7. Przewijanie z odstępem końcowym.

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

Aby uzyskać podobne efekty w przypadku VerticalPager, możesz ustawić wartości top i bottom. Wartość 32.dp jest tu używana tylko jako przykład. Możesz ustawić dowolną wartość dla każdego z wymiarów odstępu.

Dostosowywanie zachowania przewijania

Domyślne elementy kompozycyjne HorizontalPager i VerticalPager określają, jak gesty przewijania działają w przypadku przewijania. Możesz jednak dostosować i zmienić ustawienia domyślne, takie jak pagerSnapDistance czy flingBehavior.

Odległość przyciągania

Domyślnie HorizontalPager i VerticalPager ustawiają maksymalną liczbę stron, o które można przewinąć za pomocą gestu przewijania, na 1 stronę naraz. Aby to zmienić this, ustaw pagerSnapDistance w flingBehavior:

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

Tworzenie przewijania z automatycznym przechodzeniem

W tej sekcji dowiesz się, jak utworzyć w Compose przewijanie z automatycznym przechodzeniem i wskaźnikami stron. Kolekcja elementów jest automatycznie przewijana w poziomie, ale użytkownicy mogą też ręcznie przesuwać elementy. Jeśli użytkownik wejdzie w interakcję z przewijaniem, automatyczne przechodzenie zostanie zatrzymane.

Podstawowy przykład

Poniższe fragmenty kodu tworzą podstawową implementację przewijania z automatycznym przechodzeniem i wskaźnikiem wizualnym, w którym każda strona jest renderowana w innym kolorze:

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

Najważniejsze informacje o kodzie

  • Funkcja AutoAdvancePager tworzy widok przewijania w poziomie z automatycznym przechodzeniem. Jako dane wejściowe przyjmuje listę obiektów Color, które są używane jako kolory tła każdej strony.
  • pagerState jest tworzony za pomocą rememberPagerState, który przechowuje stan przewijania.
  • pagerIsDragged i pageIsPressed śledzą interakcje użytkownika.
  • LaunchedEffect automatycznie przechodzi do następnej strony co 2 sekundy, chyba że użytkownik przeciągnie przewijanie lub naciśnie jedną ze stron.
  • HorizontalPager wyświetla listę stron, z których każda zawiera element kompozycyjny Text wyświetlający numer strony. Modyfikator wypełnia stronę, ustawia kolor tła z pageItems i sprawia, że strona jest klikalna.

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

Najważniejsze informacje o kodzie

  • Element kompozycyjny Box pełni rolę elementu głównego i zawiera Row aby ułożyć wskaźniki stron w poziomie.
  • Niestandardowy wskaźnik strony jest wyświetlany jako wiersz kół, gdzie każdy Box przycięty do CircleShape reprezentuje stronę.
  • Koło bieżącej strony jest kolorowane jako DarkGray, a pozostałe koła jako LightGray. Parametr currentPageIndex określa, które koło ma być renderowane w kolorze ciemnoszarym.

Wynik

Ten film przedstawia podstawowe przewijanie z automatycznym przechodzeniem z poprzednich fragmentów kodu:

Rysunek 8. Przewijanie z automatycznym przechodzeniem i 2-sekundowym opóźnieniem między przejściami.

Dodatkowe materiały