Pager w sekcji Utwórz

Aby przewijać treści w lewo i prawo lub w górę i dół, możesz użyć odpowiednio komponentów HorizontalPager i VerticalPager. Te komponenty mają podobne funkcje jak ViewPager w systemie wyświetlania. Domyślnie HorizontalPager zajmuje całą szerokość ekranu, VerticalPager zajmuje całą wysokość, a przeglądarka przenosi tylko jedną stronę naraz. Wszystkie te ustawienia domyślne można konfigurować.

HorizontalPager

Aby utworzyć stronę z przewijaniem poziomym 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ć stronę przewijaną 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 bezmyślne

Strony w HorizontalPagerVerticalPagerkomponowane i układane w miarę potrzeby. Gdy użytkownik przewija strony, kompozyt usunął wszystkie strony, które nie są już potrzebne.

Wczytywanie większej liczby stron poza ekranem

Domyślnie pager wczytuje tylko widoczne strony na ekranie. Aby wczytywać więcej stron poza ekranem, ustaw wartość parametru beyondBoundsPageCount na wartość większą od zera.

Przewijanie do elementu w pagerze

Aby przewinąć do określonej strony w pagerze, utwórz obiekt PagerState za pomocą obiektu rememberPagerState() i przekaż go jako parametr state do pagera. W tym stanie możesz zadzwonić doPagerState#scrollToPage()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 na stronę, 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 działa żadna animacja ani nie jest wykonywane przewijanie. Różnica między właściwością currentPage a  polega na tym, że currentPage aktualizuje się 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 podczas przewijania.

Za pomocą funkcji snapshotFlow możesz obserwować zmiany tych zmiennych i na nie reagować. Aby na przykład wysyłać zdarzenie Analytics po każdej zmianie strony, wykonaj 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 kształcie koła, możesz powtórzyć liczbę kół i zmienić ich kolor w zależności od tego, czy dana strona jest wybrana, za pomocą: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)
        )
    }
}

Strony przewijania z wskaźnikiem w kształcie koła pod treścią
Rysunek 3. Przewijanie pokazujące okrągły wskaźnik pod treścią

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

Typowym zastosowaniem jest użycie pozycji przewijania do zastosowania efektów do elementów strony przewijania. Aby dowiedzieć się, jak daleko jest dana strona od aktualnie wybranej strony, możesz użyć PagerState.currentPageOffsetFraction. Następnie możesz zastosować efekty transformacji do treści na podstawie odległości od wybranej strony.

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

Aby na przykład dostosować przezroczystość elementów w zależności od odległości od środka, zmień wartość alpha za pomocą Modifier.graphicsLayer w elemencie na stronie:

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 HorizontalPagerVerticalPager zajmują odpowiednio całą szerokość lub całą wysokość. Zmienną pageSize możesz ustawić tak, aby miała wartość Fixed, Fill (domyślnie) lub wartość obliczona na podstawie niestandardowego rozmiaru.

Aby na przykład ustawić stałą szerokość strony 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 obiekt PageSize i podziel wartość availableSpace przez 3, biorąc pod uwagę odstęp 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;

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

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

Przeglądarka z wypełnieniem na początku, która pokazuje treść wyrównaną do końca

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

Ustawienie wartości startend na tę samą wartość spowoduje wyśrodkowanie elementu w poziomie:

Stronka z wypełnieniem na początku i na końcu, na której treści są wyśrodkowane

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

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

Przeglądarka z wypełnieniem na początku i na końcu, które pokazuje treść wyrównaną do początku

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

Aby uzyskać podobne efekty dla VerticalPager, możesz ustawić wartości top i bottom. Wartość 32.dp jest tu używana tylko do celów poglądowych. Każdy z wymiarów wypełniania możesz ustawić na dowolną wartość.

Dostosowywanie zachowania przewijania

Domyślne komponenty HorizontalPagerVerticalPager określają, jak gesty przewijania działają w przypadku strony. Możesz jednak dostosować i zmienić domyślne wartości, takie jak pagerSnapDistance lub flingBehavior.

Odległość przyciągania

Domyślnie HorizontalPagerVerticalPager określają maksymalną liczbę stron, które gest przesuwania może przewinąć o jedną stronę naraz. Aby to zmienić, ustaw wartość pagerSnapDistance w ustawieniu 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 przewijanego wyświetlacza

W tej sekcji opisaliśmy, jak utworzyć automatycznie przewijany przewijacz z wskaźnikami stron w sekcji Redagowanie. Kolekcja elementów automatycznie przewija się poziomo, ale użytkownicy mogą też ręcznie przeciągać elementy. Jeśli użytkownik wejdzie w interakcję z pagerem, automatyczne przechodzenie do kolejnych stron zostanie wstrzymane.

Przykład podstawowy

Poniższe fragmenty kodu tworzą podstawową implementację automatycznego przewijania z wizualnym wskaźnikiem, w której 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 stron z automatycznym przechodzeniem. 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ą rememberPagerState, który przechowuje stan strony.
  • pagerIsDraggedpageIsPressed śledzą interakcje użytkowników.
  • LaunchedEffect automatycznie przesuwa strony co 2 sekundy, chyba że użytkownik przeciągnie pager lub naciśnie jedną ze stron.
  • HorizontalPager wyświetla listę stron, z których każda ma komponent Text z numerem strony. Modyfikator wypełnia stronę, ustawia kolor tła z pageItems i czyni stronę klikalną.

@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 jest używany komponent Box.
    • Wewnątrz Box komponent Row rozmieszcza wskaźniki stron poziomo.
  • Wskaźnik strony niestandardowej jest wyświetlany jako rząd okręgów, gdzie każdy Boxprzycięty do circle reprezentuje stronę.
  • Krąg bieżącej strony jest oznaczony kolorem DarkGray, a inne LightGray. Parametr currentPageIndex określa, które koło będzie renderowane w ciemnoszarym kolorze.

Wynik

Ten film pokazuje podstawowy automatycznie przesuwający się wyświetlacz z poprzednich fragmentów:

Rysunek 1. automatycznie przesuwający się wyświetlacz z 2-sekundowym opóźnieniem między każdą stroną.

Dodatkowe materiały