Pager w sekcji Utwórz

Aby przewijać treści w lewo i w prawo lub w górę i w dół, możesz użyć odpowiednio funkcji kompozycyjnych HorizontalPagerVerticalPager. Te funkcje kompozycyjne mają podobne funkcje do ViewPager w systemie widoków. Domyślnie HorizontalPager zajmuje całą szerokość ekranu, VerticalPager – całą wysokość, a pagery przewijają tylko jedną stronę naraz. Wszystkie te ustawienia domyślne można skonfigurować.

HorizontalPager

Aby utworzyć pager, który przewija się w lewo i w prawo, użyj tego kodu:HorizontalPager

Rysunek 1. Wersja demonstracyjna 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ć pager, który przewija się w górę i w dół, użyj tego kodu: VerticalPager

Rysunek 2. Wersja demonstracyjna VerticalPager

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

Leniwe tworzenie

Strony w HorizontalPagerVerticalPagerkomponowane na żądanie i układane w razie potrzeby. Gdy użytkownik przewija strony, komponent usuwa te, które nie są już potrzebne.

Wczytywanie większej liczby stron poza ekranem

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

Przewiń do elementu w pagerze

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

  • currentPage: Najbliższa strona do pozycji przyciągania. Domyślnie pozycja przyciągania jest na początku układu.
  • settledPage: numer strony, gdy nie jest uruchomiona 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 bez zmian, dopóki nie zakończą się wszystkie animacje.
  • targetPage: proponowana pozycja zatrzymania przewijania.

Możesz użyć funkcji snapshotFlow, aby obserwować zmiany tych zmiennych i na nie reagować. Aby na przykład wysył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 wszystkich stron, i narysuj niestandardowy wskaźnik.

Jeśli na przykład chcesz użyć prostego wskaźnika w postaci kółka, możesz powtórzyć liczbę kółek i zmienić ich kolor w zależności od tego, czy strona jest wybrana, używając tego kodu: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. Pager z okrągłym wskaźnikiem pod treścią

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

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

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

Aby na przykład dostosować krycie elementów w zależności od tego, jak daleko znajdują się od środka, zmień wartość alpha za pomocą funkcji Modifier.graphicsLayer w 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 elementy HorizontalPager i VerticalPager zajmują odpowiednio całą szerokość lub całą wysokość. Zmienną pageSize możesz ustawić tak, aby miała wartość Fixed, Fill (domyślnie) lub niestandardowe obliczenia rozmiaru.

Na przykład aby 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 dopasować rozmiar stron do rozmiaru widocznego obszaru, użyj niestandardowego obliczania rozmiaru strony. Utwórz obiekt niestandardowy PageSize 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
    }
}

Wypełnienie treści

HorizontalPagerVerticalPager obsługują zmianę dopełnienia treści, co pozwala wpływać na maksymalny rozmiar i wyrównanie stron.

Na przykład ustawienie start padding wyrównuje strony do końca:

Stronicowanie z wypełnieniem początkowym, w którym treść jest wyrównana do końca

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

Ustawienie dopełnienia startend na tę samą wartość wyśrodkuje element w poziomie:

Pager z marginesami na początku i na końcu, wyświetlający wyśrodkowaną zawartość

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

Ustawienie dopełnienia end wyrównuje strony do początku:

Stronicowanie z marginesami początkowym i końcowym, w którym treść jest wyrównana do początku

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

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

Dostosowywanie przewijania

Domyślne funkcje kompozycyjne HorizontalPagerVerticalPager określają, jak gesty przewijania działają w przypadku komponentu do stronicowania. Możesz jednak dostosować i zmienić ustawienia domyślne, takie jak pagerSnapDistance lub flingBehavior.

Odległość przyciągania

Domyślnie HorizontalPagerVerticalPager ustawiają maksymalną liczbę stron, o którą można przewinąć za pomocą gestu szybkiego przesunięcia, na 1 stronę naraz. Aby to zmienić, ustaw pagerSnapDistance na 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 automatycznie przewijanej strony

Z tej sekcji dowiesz się, jak utworzyć automatycznie przewijany pager ze wskaźnikami stron w Compose. Kolekcja elementów przewija się automatycznie w poziomie, ale użytkownicy mogą też ręcznie przesuwać między nimi. Jeśli użytkownik wejdzie w interakcję z pagerem, automatyczne przełączanie zostanie zatrzymane.

Podstawowy przykład

Poniższe fragmenty kodu tworzą podstawową implementację automatycznie przewijanego pagera ze 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 z przewijaniem w poziomie i automatycznym przechodzeniem do następnej strony. Jako dane wejściowe przyjmuje listę obiektów Color, które są używane jako kolory tła poszczególnych stron.
  • pagerState jest tworzony za pomocą funkcji rememberPagerState, która przechowuje stan stronicowania.
  • pagerIsDraggedpageIsPressed śledzą interakcje użytkowników.
  • LaunchedEffect automatycznie przełącza strony co 2 sekundy, chyba że użytkownik przeciągnie stronę lub naciśnie jedną z nich.
  • HorizontalPager wyświetla listę stron, z których każda zawiera Text komponent wyświetlający numer strony. Modyfikator wypełnia stronę, ustawia kolor tła z pageItems i sprawia, że można ją kliknąć.

@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

  • Jako element główny używany jest komponent Box.
    • W komponencie Box komponent Row rozmieszcza wskaźniki stron w poziomie.
  • Niestandardowy wskaźnik strony jest wyświetlany jako rząd kółek, z których każde Box przycięte do circle reprezentuje stronę.
  • Okrąg bieżącej strony jest oznaczony kolorem DarkGray, a pozostałe okręgi – kolorem LightGray. Parametr currentPageIndex określa, który okrąg ma być renderowany w kolorze ciemnoszarym.

Wynik

Ten film przedstawia podstawowy pager z automatycznym przełączaniem stron z poprzednich fragmentów kodu:

Rysunek 1. Automatycznie przewijany pager z 2-sekundowym opóźnieniem między kolejnymi stronami.

Dodatkowe materiały