Что такое «бесконечный» пейджер?

«Бесконечный» пейджер (endless/infinite pager) — элемент пользовательского интерфейса, создающий иллюзию бесконечного скроллинга за счет непрерывной цикличности просмотра контента. Он часто используется в слайдерах (каруселях) изображений, новостных лентах, галереях товаров — везде, где выгоден непрерывный просмотр.

Имитация эффекта бесконечного скроллинга

Как сделать обычный пейджер “бесконечным”

Любой обычный пейджер можно превратить в «бесконечный», то есть пейджер с бесконечным скроллингом, используя простые приемы.

Как это работает

Принцип работы «бесконечного пейджера» заключается в создании иллюзии бесконечного скроллинга. Он достигается с помощью следующих стратегий.

  1. Расширение диапазона страниц. Умножая общее количество страниц на большую константу, можно создать гораздо больший диапазон страниц. Это гарантирует, что пользователи смогут долгое время прокручивать страницу в обоих направлениях, не доходя до конца.
  1. Установление начальной страницы в середине диапазона страниц. Если начать с середины, пользователи потратят значительное время на прокрутку, прежде чем доберутся до конца.
  1. Обертывание индекса. При отображении контента индекс текущей страницы обертывается с помощью оператора модуля (%). Это позволяет многократно переходить к контенту независимо от того, насколько далеко прокручивает страницу пользователь.

Описанные стратегии, несмотря на свою простоту, позволяют создать плавную, зацикленную навигацию.

Важное замечание: при выборе большой константы важно выбрать большое число, чтобы создать иллюзию бесконечного скроллинга, но не настолько большое, чтобы вызвать проблемы с производительностью или ошибку ANR (application not responding — приложение не отвечает). Это связано с тем, что Jetpack Compose кэширует страницы, и использование слишком большого числа может привести к увеличению потребления памяти и потенциальному замедлению работы приложения.

Пример кода

Демонстрацию того, как преобразовать обычный пейджер в «бесконечный» с помощью Jetpack Compose, можно посмотреть в репозитории на Github.

Исходный код

Вот исходный код реализации пейджера с помощью Jetpack Compose:

@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun AnimatedViewPager(
    modifier: Modifier = Modifier,
    pageSize: Dp,
    @DrawableRes drawables: List<Int>,
) {
    val pagerState = rememberPagerState(
        initialPage = 0,
        initialPageOffsetFraction = 0f,
        pageCount = { drawables.size },
    )

    var currentPageIndex by remember { mutableIntStateOf(0) }
    val hapticFeedback = LocalHapticFeedback.current
    LaunchedEffect(pagerState) {
        snapshotFlow { pagerState.currentPage }.collect { currentPage ->
            if (currentPageIndex != currentPage) {
                hapticFeedback.performHapticFeedback(
                    hapticFeedbackType = HapticFeedbackType.LongPress,
                )
                currentPageIndex = currentPage
            }
        }
    }

    HorizontalPager(
        modifier = modifier,
        state = pagerState,
        contentPadding = PaddingValues(horizontal = pageSize),
        verticalAlignment = Alignment.CenterVertically,
    ) { thisPageIndex ->
        PageLayout(
            modifier = Modifier
                .size(size = pageSize)
                .pagerAnimation(
                    pagerState = pagerState,
                    thisPageIndex = thisPageIndex,
                ),
            drawable = drawables[thisPageIndex],
        )
    }
}

Модифицированный код для реализации «бесконечного» пейджинга

Вот изменения, необходимые для преобразования обычного пейджера в «бесконечный»:

@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun AnimatedViewPager(
    modifier: Modifier = Modifier,
    pageSize: Dp,
    @DrawableRes drawables: List<Int>,
) {
    val endlessPagerMultiplier = 1000
    val pageCount = endlessPagerMultiplier * drawables.size
    val initialPage = pageCount / 2

    val pagerState = rememberPagerState(
        initialPage = initialPage,
        initialPageOffsetFraction = 0f,
        pageCount = { pageCount },
    )

    var currentPageIndex by remember { mutableIntStateOf(initialPage) }
    val hapticFeedback = LocalHapticFeedback.current
    LaunchedEffect(pagerState) {
        snapshotFlow { pagerState.currentPage }.collect { currentPage ->
            if (currentPageIndex != currentPage) {
                hapticFeedback.performHapticFeedback(
                    hapticFeedbackType = HapticFeedbackType.LongPress,
                )
                currentPageIndex = currentPage
            }
        }
    }

    HorizontalPager(
        modifier = modifier,
        state = pagerState,
        contentPadding = PaddingValues(horizontal = pageSize),
        verticalAlignment = Alignment.CenterVertically,
    ) { absolutePageIndex ->
        val resolvedPageContentIndex = absolutePageIndex % drawables.size

        PageLayout(
            modifier = Modifier
                .size(size = pageSize)
                .pagerAnimation(
                    pagerState = pagerState,
                    thisPageIndex = absolutePageIndex,
                ),
            drawable = drawables[resolvedPageContentIndex],
        )
    }
}

Ключевые изменения в коде

1. Увеличение количества страниц

Умножаем исходное количество страниц на большую константу (endlessPagerMultiplier) и устанавливаем initialPage в середину этого расширенного диапазона.

val endlessPagerMultiplier = 1000
val pageCount = endlessPagerMultiplier * drawables.size
val initialPage = pageCount / 2

val pagerState = rememberPagerState(
initialPage = initialPage,
initialPageOffsetFraction = 0f,
pageCount = { pageCount },
)

2. Обертывание индекса

Используем оператор модуля, чтобы обернуть индекс страницы при определении того, какой контент отображать.

val resolvedPageContentIndex = absolutePageIndex % drawables.size

Заключение

Достаточно нескольких строк кода, чтобы превратить стандартный пейджер в «бесконечный». Эти минимальные изменения в коде позволят значительно повысить удобство программного продукта и вовлеченность пользователей.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Ryan W: Implementing an Endless Pager in Jetpack Compose

Предыдущая статья28 суперполезных фрагментов Python-кода для решения повседневных задач 
Следующая статьяОператоры Ruby: звездочка * и двойная звездочка **