Как обнаружить попытку провести за пределы HorizontalPager?

Я хотел бы определить, когда пользователь пытается пролистать последнюю страницу HorizontalPager. В этот момент я бы хотел, чтобы он прокрутился обратно на первую страницу. Мне не нужен бесконечный пейджер, я просто хочу, чтобы он прокручивался обратно к началу при пролистывании последней страницы.

Я предполагаю, что это предполагает настройку какого-то детектора жестов ввода указателя, но я не уверен, как это сделать, не нарушив встроенное обнаружение жестов в HorizonalPager.

1
0
200
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать Modifier.pointerInput и awaitPointerEvent(pass = PointerEventPass.Initial). PointerEventPass.Initial гарантирует, что Modifier.pointerInput вызывается перед внутренним из HorizontalPager через Modifier.scroll. Кроме того, поскольку мы не обрабатываем никаких событий, мы не вмешиваемся в работу пейджера. Вы можете обратиться к ответам ниже для получения более подробной информации о жестах и ​​распространении событий.

https://stackoverflow.com/a/70847531/5457853

Jetpack Compose Intercept сведение/увеличение масштаба в дочернем макете

Однако, поскольку Pager объединяет элементы на основе BeyondPageSomething, они снова изменили имя этого параметра на 1.7, он будет анимировать одну или несколько страниц с помощью pager.animateScrollToPage

Результат

Демо

@Preview
@Composable
fun PagerScrollSample() {

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp)
    ) {

        val pagerState = rememberPagerState {
            5
        }

        var shouldScrollToFirstPage by remember {
            mutableStateOf(false)
        }

        LaunchedEffect(shouldScrollToFirstPage) {
            if (shouldScrollToFirstPage) {
                delay(100)
                pagerState.animateScrollToPage(0)
                shouldScrollToFirstPage = false
            }
        }

        Text("shouldScrollToFirstPage: $shouldScrollToFirstPage")
        HorizontalPager(
            userScrollEnabled = shouldScrollToFirstPage.not(),
            modifier = Modifier
                .pointerInput(Unit) {
                    awaitEachGesture {
                        awaitFirstDown()
                        shouldScrollToFirstPage = false

                        do {
                            val event: PointerEvent = awaitPointerEvent(
                                pass = PointerEventPass.Initial
                            )

                            event.changes.forEach {

                                if (pagerState.currentPage == 4 &&                          
                                    pagerState.currentPage == pagerState.settledPage &&

                                    // current position of finger
                                    it.position.x < 200f &&
                                    shouldScrollToFirstPage.not()
                                ) {
                                    shouldScrollToFirstPage = true
                                }
                            }

                        } while (event.changes.any { it.pressed })


                        // User lifts pointer, you can animate here as well
//                        if (pagerState.currentPage == 4 &&
//                            pagerState.currentPageOffsetFraction == 0f
//                        ) {
//                            shouldScrollToFirstPage = true
//                        }


                    }
                },
            state = pagerState,
            pageSpacing = 16.dp,
        ) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
                    .background(Color.LightGray, RoundedCornerShape(16.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Page $it",
                    fontSize = 28.sp
                )
            }
        }
    }
}

Другая альтернатива с частично видимыми и кликабельными элементами.

@Preview
@Composable
private fun PagerScrollSample2() {

    val pagerState = rememberPagerState {
        5
    }

    var shouldScrollToFirstPage by remember {
        mutableStateOf(false)
    }

    val coroutineScope = rememberCoroutineScope()

    BoxWithConstraints {

        val pageSpacing = 16.dp
        val pageWidth = maxWidth - pageSpacing - 32.dp

        HorizontalPager(
            userScrollEnabled = shouldScrollToFirstPage.not(),
            contentPadding = PaddingValues(horizontal = 16.dp),
            pageSize = PageSize.Fixed(pageWidth),
            modifier = Modifier
                .pointerInput(Unit) {
                    awaitEachGesture {
                        val down = awaitFirstDown(pass = PointerEventPass.Initial)
                        shouldScrollToFirstPage = false

                        val firstTouchX = down.position.x

                        do {
                            val event: PointerEvent = awaitPointerEvent(
                                pass = PointerEventPass.Initial
                            )

                            event.changes.forEach {

                                val diff = firstTouchX - it.position.x

                                if (pagerState.currentPage == 4 &&
                                    pagerState.currentPage == 
                                    pagerState.settledPage &&
                                    // Scroll if user scrolled 10% from first touch position
                                    // or pointer is at the left of 20% of page
                                    (diff > size.width * .10f ||
                                            it.position.x < size.width * .2f) &&
                                    shouldScrollToFirstPage.not()
                                ) {
                                    coroutineScope.launch {
                                        shouldScrollToFirstPage = true
                                        pagerState.animateScrollToPage(
                                            0,
                                            animationSpec = tween(500)
                                        )
                                        shouldScrollToFirstPage = false
                                    }
                                }
                            }

                        } while (event.changes.any { it.pressed })
                    }
                },
            state = pagerState,
            pageSpacing = pageSpacing,
        ) {

            val context = LocalContext.current

            Box(
                modifier = Modifier
                    .clickable {
                        Toast.makeText(context, "Clicked $it", Toast.LENGTH_SHORT).show()
                    }
                    .fillMaxWidth()
                    .height(200.dp)
                    .background(Color.LightGray, RoundedCornerShape(16.dp)),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Page $it",
                    fontSize = 28.sp
                )
            }
        }
    }

    Button(
        modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(),
        onClick = {
            coroutineScope.launch {
                pagerState.animateScrollToPage(0)
            }
        }
    ) {
        Text("Scroll to first page")
    }
}

Спасибо! Есть ли способ заставить это работать, если содержимое моей страницы кликабельно? Например, на каждой из моих страниц есть составная карточка, в которой я передаю лямбду onClick. Когда я прокручиваю последнюю карточку, чтобы вернуться на первую страницу, ничего не происходит. Если я удалю onClick с карты, все будет работать нормально.

SilentByte 16.06.2024 23:52
awaitFirstDown принимает параметр pass, а также устанавливает его на awaitFirstDown( pass = PointerEventPass.Initial), и он получит щелчок раньше, чем дочерние Composables, и, поскольку мы не используем кликабельность, тоже будет работать нормально
Thracian 17.06.2024 03:56

Другие вопросы по теме

Я пытаюсь перейти к другой составной функции, которая работает, но пользовательский интерфейс первой главной страницы зависает или фиксируется на экране
Отображать нижнюю навигацию только для определенных составных элементов в Jetpack Compose
Перейдите к следующему экрану после завершения вызова API
Как запросить базу данных комнаты из составного объекта, чтобы получить подробную информацию об элементе
Могу ли я добавить сцену Unity как составную?
Разрешить пользовательской оболочке TextInput поддерживать тестирование пользовательского интерфейса «performTextInput»
Почему мы не используем ConstraintLayout в компоновке Jetpack так же, как в XML?
Android получает ошибку Hilt при запуске приложения
Compose: Как сделать так, чтобы Snackbar не находился над FloatingActionButton?
Java.lang.IllegalStateException: чтение состояния, созданного после того, как был сделан снимок, или в снимке, который еще не был применен