Pager w sekcji Utwórz

Aby przeglądać treści w poziomie lub w pionie, możesz użyć funkcji kompozycyjnych HorizontalPager i VerticalPager. Mają one podobne funkcje jak ViewPager w systemie widoków. Domyślnie HorizontalPager zajmuje całą szerokość ekranu, a VerticalPager – całą wysokość. Paginatory również przesuwają tylko 1 stronę naraz. Wszystkie te ustawienia domyślne można konfigurować.

HorizontalPager

Aby utworzyć pager, który przewija się w poziomie w lewo i w prawo, użyj 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 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 HorizontalPager i VerticalPager są tworzone i układane z opóźnieniem, gdy jest to wymagane. Gdy użytkownik przewija strony, funkcja kompozycyjna usuwa te, które nie są już potrzebne.

Wczytywanie większej liczby stron poza ekranem

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

Przewijanie do elementu w paginatorze

Aby przewinąć do określonej strony w komponencie Pager, utwórz obiekt PagerState za pomocą rememberPagerState() i przekaż go jako parametr state do komponentu Pager. Możesz wywołać PagerState#scrollToPage() w tym stanie wewnątrz 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 animacja ani przewijanie. Różni się ona od właściwości currentPage tym, że currentPage jest aktualizowana natychmiast, gdy strona jest wystarczająco blisko pozycji przyciągania, ale settledPage pozostaje taka sama, dopóki nie zakończą się wszystkie animacje.
  • targetPage: proponowana pozycja zatrzymania ruchu przewijania.

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

Aby na przykład utworzyć wskaźnik kołowy, możesz powtórzyć liczbę kółek i zmienić ich kolor 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)
        )
    }
}

Pager 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

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

Rysunek 4. Stosowanie przekształceń do zawartości pagera

Jeśli na przykład chcesz dostosować przezroczystość 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 pełną szerokość lub pełną wysokość. Możesz ustawić zmienną pageSize tak, aby miała wartość Fixed, Fill (domyślnie) lub niestandardowe obliczanie rozmiaru.

Aby na przykład ustawić stałą szerokość strony na 100.dp:

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

Aby dostosować rozmiar stron do rozmiaru widocznego obszaru, użyj niestandardowego obliczania rozmiaru strony. Utwórz niestandardowy obiekt 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
    }
}

Dopełnienie treści

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

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

Pager z dopełnieniem na początku, który wyświetla treści wyrównane do końca
Rysunek 5. Pager z dopełnieniem początkowym.

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

Ustawienie dopełnienia start i end na tę samą wartość powoduje wyśrodkowanie elementu w poziomie:

Paginator z dopełnieniem na początku i na końcu, pokazujący zawartość wyśrodkowaną
Rysunek 6. Pager z dopełnieniem poziomym.

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:

Pager z dopełnieniem na początku i na końcu, pokazujący zawartość wyrównaną do początku
Rysunek 7. Pager z dopełnieniem na końcu.

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 używana tylko jako przykład. Możesz ustawić dowolną wartość dla każdego z wymiarów dopełnienia.

Dostosowywanie działania przewijania

Domyślne funkcje kompozycyjne HorizontalPager i VerticalPager określają, jak gesty przewijania działają z pagerem. Możesz jednak dostosować i zmienić ustawienia domyślne, takie jak pagerSnapDistance lub flingBehavior.

Odległość przyciągania

Domyślnie HorizontalPager i VerticalPager ustawiają maksymalną liczbę stron, które można przewinąć gestem 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 pagera z automatycznym przechodzeniem

W tej sekcji opisano, jak utworzyć w Compose automatycznie przewijany pager ze wskaźnikami stron. Kolekcja elementów przewija się automatycznie w poziomie, ale użytkownicy mogą też ręcznie przesuwać palcem między elementami. Jeśli użytkownik wejdzie w interakcję z pagerem, automatyczne przechodzenie 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 automatycznym przewijaniem poziomym. Jako dane wejściowe przyjmuje listę obiektów Color, które są używane jako kolory tła dla każdej strony.
  • pagerState jest tworzony za pomocą rememberPagerState, który przechowuje stan pagera.
  • pagerIsDragged i pageIsPressed śledzą interakcje użytkowników.
  • LaunchedEffect automatycznie przechodzi do kolejnej strony co 2 sekundy, chyba że użytkownik przeciągnie stronę 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 działa jako element główny i zawiera element Row, który umożliwia poziome rozmieszczenie wskaźników strony.
  • Niestandardowy wskaźnik strony jest wyświetlany jako wiersz okręgów, gdzie każdy Box przycięty do CircleShape reprezentuje stronę.
  • Kółko bieżącej strony ma kolor DarkGray, a pozostałe kółka są LightGray. Parametr currentPageIndex określa, które kółko jest ciemnoszare.

Wynik

Ten film przedstawia podstawowy pager z automatycznym przechodzeniem do przodu z poprzednich fragmentów kodu:

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

Dodatkowe materiały