Для горизонтального или вертикального перелистывания контента можно использовать composable-функции HorizontalPager и VerticalPager. Они выполняют функции, аналогичные ViewPager в системе View. По умолчанию HorizontalPager занимает всю ширину экрана, а VerticalPager – всю высоту. Пейджеры также пролистывают только одну страницу за раз. Все эти настройки по умолчанию можно изменить.
HorizontalPager
Чтобы создать пейджер, который прокручивается по горизонтали влево и вправо, используйте 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
Чтобы создать пейджер, который прокручивается вверх и вниз, используйте VerticalPager:
VerticalPager
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) VerticalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
Отложенное создание
Страницы в HorizontalPager и VerticalPager компонуются и размещаются по мере необходимости. Когда пользователь прокручивает страницы, composable-функция удаляет те, которые больше не нужны.
Загружать больше страниц за пределами экрана
По умолчанию пейджер загружает только видимые страницы на экране. Чтобы загружать больше страниц за пределами экрана, задайте для параметра beyondBoundsPageCount значение больше нуля.
Прокрутка до элемента в пейджере
Чтобы перейти к определенной странице, создайте объект PagerState с помощью rememberPagerState() и передайте его в качестве параметра state. Вы можете вызвать PagerState#scrollToPage() в этом состоянии внутри 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") }
Если вы хотите анимировать переход на страницу, используйте функцию 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") }
Как получать уведомления об изменении статуса страницы
PagerState имеет три свойства с информацией о страницах: currentPage, settledPage и targetPage.
currentPage– ближайшая страница к позиции привязки. По умолчанию позиция привязки находится в начале макета.settledPage– номер страницы, когда не выполняется анимация или прокрутка. Это свойство отличается отcurrentPageтем, чтоcurrentPageобновляется сразу, если страница находится достаточно близко к позиции привязки, аsettledPageостается неизменным, пока не завершится вся анимация.targetPage– предлагаемая позиция остановки для прокрутки.
Чтобы отслеживать изменения этих переменных и реагировать на них, можно использовать функцию snapshotFlow. Например, чтобы отправлять событие аналитики при каждом изменении страницы, можно сделать следующее:
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") }
Как добавить индикатор страницы
Чтобы добавить индикатор на страницу, используйте объект PagerState. Он позволяет получить информацию о том, какая страница выбрана из общего числа страниц, и нарисовать собственный индикатор.
Например, чтобы создать индикатор в виде круга, вы можете повторить количество кругов и изменить цвет круга в зависимости от того, выбрана ли страница, используя 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) ) } }
Применение эффектов прокрутки к контенту
Распространенным вариантом использования является использование позиции прокрутки для применения эффектов к вашему пейджеру.
предметы. Чтобы узнать, насколько далеко страница находится от выбранной, используйте PagerState.currentPageOffsetFraction. Затем вы можете применить эффекты преобразования к контенту в зависимости от расстояния до выбранной страницы.
Например, чтобы настроить прозрачность элементов в зависимости от того, насколько далеко они находятся от центра, измените alpha с помощью Modifier.graphicsLayer для элемента внутри пейджера:
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 } }
Специальные размеры страниц
По умолчанию HorizontalPager и VerticalPager занимают всю ширину или высоту соответственно. Для переменной pageSize можно задать расчет размера по умолчанию (Fill), а также на основе Fixed или собственных настроек.
Например, чтобы задать фиксированную ширину страницы 100.dp:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, pageSize = PageSize.Fixed(100.dp) ) { page -> // page content }
Чтобы размер страниц зависел от размера области просмотра, используйте специальный расчет размера страницы. Создайте специальный объект PageSize и разделите availableSpace на три, учитывая расстояние между элементами:
private val threePagesPerViewport = object : PageSize { override fun Density.calculateMainAxisPageSize( availableSpace: Int, pageSpacing: Int ): Int { return (availableSpace - 2 * pageSpacing) / 3 } }
Поля контента
HorizontalPager и VerticalPager поддерживают изменение отступов контента, что позволяет влиять на максимальный размер и выравнивание страниц.
Например, если задать отступ start, страницы будут выравниваться по правому краю:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(start = 64.dp), ) { page -> // page content }
Если задать одинаковые значения для полей start и end, элемент будет выровнен по центру по горизонтали:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(horizontal = 32.dp), ) { page -> // page content }
Если задать отступ end, страницы будут выравниваться по началу:
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(end = 64.dp), ) { page -> // page content }
Чтобы добиться аналогичного эффекта для VerticalPager, можно задать значения top и bottom. Значение 32.dp используется только в качестве примера. Вы можете задать любое значение для каждого из полей.
Настройка прокрутки
Стандартные composable-функции HorizontalPager и VerticalPager определяют, как жесты прокрутки работают с пейджером. Однако вы можете изменить настройки по умолчанию, например pagerSnapDistance или flingBehavior.
Расстояние привязки
По умолчанию HorizontalPager и VerticalPager устанавливают максимальное количество страниц, которые можно прокрутить за один раз, равным одной странице. Чтобы изменить это, задайте для flingBehavior атрибут pagerSnapDistance:
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) } }
Как создать пейджер с автопереходом
В этом разделе описывается, как создать пейджер с автоматическим продвижением и индикаторами страниц в Compose. Объекты в коллекции автоматически прокручиваются по горизонтали, но пользователи также могут пролистывать их вручную. Если пользователь взаимодействует с пейджером, автоматическое переключение прекращается.
Простой пример
Вместе эти фрагменты создают базовую реализацию пейджера с автопереходом и визуальным индикатором, в котором каждая страница отображается разным цветом:
@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) } }
Важные сведения о коде
- Функция
AutoAdvancePagerсоздает горизонтальную прокрутку с автоматическим переходом. В качестве входных данных он принимает список объектовColor, которые используются в качестве фоновых цветов для каждой страницы. pagerStateсоздается с помощьюrememberPagerState, который содержит состояние пейджера.pagerIsDraggedиpageIsPressedотслеживают взаимодействие с пользователем.LaunchedEffectавтоматически переключает страницы каждые две секунды, если пользователь не перетаскивает страницу или не нажимает на одну из них.HorizontalPagerпоказывает список страниц, каждая из которых содержит composable-функциюText, отображающую номер страницы. Модификатор заполняет страницу, задает цвет фона изpageItemsи делает страницу кликабельной.
@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) ) } } } }
Важные сведения о коде
- Корневым элементом является composable-функция
Box, которая содержитRowдля горизонтального расположения индикаторов страниц. - Специальный индикатор страницы отображается в виде ряда кругов, где каждый
Box, обрезанный доCircleShape, представляет страницу. - Кружок текущей страницы окрашен в
DarkGray, а остальные – вLightGray. ПараметрcurrentPageIndexопределяет, какой круг будет темно-серым.
Результат
В этом видео показан базовый пейджер с автопереходом из предыдущих фрагментов кода: