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

Чтобы пролистывать контент влево и вправо или вверх и вниз, можно использовать соответственно компонуемые 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 устанавливают максимальное количество страниц, которые жест fling может прокручивать, по одной странице за раз. Чтобы изменить это, установите 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 composable, отображающий номер страницы. Модификатор заполняет страницу, устанавливает цвет фона из 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 %} ,

Чтобы пролистывать контент влево и вправо или вверх и вниз, можно использовать соответственно компонуемые 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 устанавливают максимальное количество страниц, которые жест fling может прокручивать, по одной странице за раз. Чтобы изменить это, установите 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 composable, отображающий номер страницы. Модификатор заполняет страницу, устанавливает цвет фона из 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 %} ,

Чтобы пролистывать контент влево и вправо или вверх и вниз, можно использовать соответственно компонуемые 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 устанавливают максимальное количество страниц, которые жест fling может прокручивать, по одной странице за раз. Чтобы изменить это, установите 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 composable, отображающий номер страницы. Модификатор заполняет страницу, устанавливает цвет фона из 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 %}