Я хотел бы определить, когда пользователь пытается пролистать последнюю страницу HorizontalPager. В этот момент я бы хотел, чтобы он прокрутился обратно на первую страницу. Мне не нужен бесконечный пейджер, я просто хочу, чтобы он прокручивался обратно к началу при пролистывании последней страницы.
Я предполагаю, что это предполагает настройку какого-то детектора жестов ввода указателя, но я не уверен, как это сделать, не нарушив встроенное обнаружение жестов в HorizonalPager.
Вы можете использовать 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")
}
}
awaitFirstDown
принимает параметр pass, а также устанавливает его на awaitFirstDown( pass = PointerEventPass.Initial)
, и он получит щелчок раньше, чем дочерние Composables, и, поскольку мы не используем кликабельность, тоже будет работать нормально
Спасибо! Есть ли способ заставить это работать, если содержимое моей страницы кликабельно? Например, на каждой из моих страниц есть составная карточка, в которой я передаю лямбду
onClick
. Когда я прокручиваю последнюю карточку, чтобы вернуться на первую страницу, ничего не происходит. Если я удалюonClick
с карты, все будет работать нормально.