Чтобы перелистывать контент по горизонтали или вертикали, можно использовать композиции HorizontalPager и VerticalPager. Они выполняют те же функции, что и ViewPager в системе просмотра. По умолчанию 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 создаются по мере необходимости. Когда пользователь прокручивает страницы, компонент удаляет ненужные страницы.
Загрузка большего количества страниц за пределами экрана
По умолчанию загружаются только видимые на экране страницы. Чтобы загрузить больше страниц за пределами экрана, задайте для параметра 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 значение Fixed, Fill (по умолчанию) или указать собственный способ расчета размера.
Например, чтобы задать фиксированную ширину страницы 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 }
Вы можете задать значения top и bottom, чтобы получить аналогичный эффект для VerticalPager. Значение 32.dp приведено здесь только в качестве примера. Вы можете задать для каждого из полей любое значение.
Как настроить прокрутку
Компонуемые функции HorizontalPager и VerticalPager по умолчанию определяют, как жесты прокрутки работают с пейджером. Однако вы можете изменить настройки по умолчанию, например pagerSnapDistance или flingBehavior.
Расстояние привязки
По умолчанию HorizontalPager и VerticalPager позволяют прокручивать жестом проведения по экрану не более одной страницы за раз. Чтобы изменить это значение, задайте атрибут pagerSnapDistance для элемента 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) } }
Как создать автоматически перелистываемый пейджер
В этом разделе рассказывается, как создать автоматически пролистываемый пейджер с индикаторами страниц в 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) ) } } } }
Ключевые моменты
- Компонуемый элемент
Boxвыступает в качестве корневого элемента и содержитRowдля горизонтального расположения индикаторов страниц. - Специальный индикатор страниц представляет собой ряд кругов, где каждый
Boxобрезан доCircleShapeи обозначает страницу. - Кружок текущей страницы окрашен в
DarkGray, а остальные – вLightGray. ПараметрcurrentPageIndexопределяет, какой круг будет отображаться темно-серым цветом.
Результат
В этом видео показан основной пейджер с автоматической прокруткой из предыдущих фрагментов кода: