Пейджер в режиме создания сообщения

Для пролистывания контента слева направо или сверху вниз можно использовать компонуемые объекты HorizontalPager и VerticalPager соответственно. Эти компонуемые объекты имеют функции, аналогичные ViewPager в системе представлений. По умолчанию HorizontalPager занимает всю ширину экрана, VerticalPager — всю высоту, а пейджеры перелистывают только одну страницу за раз. Все эти значения по умолчанию можно настроить.

HorizontalPager

Чтобы создать пейджер, прокручивающийся горизонтально влево и вправо, используйте HorizontalPager :

Рисунок 1. Демонстрация 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 :

Рисунок 2. Демонстрация 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)
        )
    }
}

Пейджер показывает круглый индикатор под содержимым
Рисунок 3. Пейджер с круговым индикатором под содержимым.

Применить эффекты прокрутки элементов к контенту

Распространенный вариант использования — применение эффектов к элементам пейджера с помощью положения прокрутки. Чтобы узнать, насколько далеко находится страница от текущей выбранной, можно использовать PagerState.currentPageOffsetFraction . Затем можно применить эффекты преобразования к контенту в зависимости от расстояния до выбранной страницы.

Рисунок 4. Применение преобразований к содержимому Pager

Например, чтобы настроить непрозрачность элементов в зависимости от их удаленности от центра, измените 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 отображает список страниц, каждая из которых содержит 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 .
    • Внутри Box компонуемый элемент Row располагает индикаторы страниц горизонтально.
  • Пользовательский индикатор страницы отображается в виде ряда кругов, где каждый Box , прикреплённый к circle представляет собой страницу.
  • Круг текущей страницы окрашен в DarkGray , а остальные — в LightGray . Параметр currentPageIndex определяет, какой круг будет отображаться тёмно-серым.

Результат

В этом видео показана базовая функция автоматической перелистывания страниц из предыдущих фрагментов:

Рисунок 1. Автоматически перелистывающий пейджер с двухсекундной задержкой между перелистыванием страниц.

Дополнительные ресурсы

{% дословно %} {% endverbatim %}