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:
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:
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 VerticalPager są tworzone
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ścicurrentPage, ponieważcurrentPagenatychmiast się aktualizuje, jeśli strona jest wystarczająco blisko pozycji przyciągania, alesettledPagepozostaje 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) ) } }
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.
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:
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:
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:
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
AutoAdvancePagertworzy widok przewijania w poziomie z automatycznym przechodzeniem. Jako dane wejściowe przyjmuje listę obiektówColor, które są używane jako kolory tła każdej strony. pagerStatejest tworzony za pomocąrememberPagerState, który przechowuje stan przewijania.pagerIsDraggedipageIsPressedśledzą interakcje użytkownika.LaunchedEffectautomatycznie przechodzi do następnej strony co 2 sekundy, chyba że użytkownik przeciągnie przewijanie lub naciśnie jedną ze stron.HorizontalPagerwyświetla listę stron, z których każda zawiera element kompozycyjnyTextwyświetlający numer strony. Modyfikator wypełnia stronę, ustawia kolor tła zpageItemsi 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
Boxpełni rolę elementu głównego i zawieraRowaby ułożyć wskaźniki stron w poziomie. - Niestandardowy wskaźnik strony jest wyświetlany jako wiersz kół, gdzie każdy
Boxprzycięty doCircleShapereprezentuje stronę. - Koło bieżącej strony jest kolorowane jako
DarkGray, a pozostałe koła jakoLightGray. ParametrcurrentPageIndexokreś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: