Вот как это выглядит

И вы можете смахивать эти карточки вот так.
[img]https:// i.sstatic.net/485HW6Lj.jpg[/img]
Проблема в том, что когда я нажимаю на горизонтальный пейджер, он перехватывает все жесты перетаскивания, я хочу иметь возможность пролистывать его перепрокрутка. Если я на первом изображении - должен быть включен свайп влево, а если я на последнем -> свайп вправо.
Вот код горизонтального пейджер с модификатором смахивания:
Код: Выделить всё
Card(
modifier = modifier
.swipableCard(
state = swipeState,
onSwiped = {
when (it) {
Direction.Left -> onLeftSwipe()
Direction.Right -> onRightSwipe()
else -> {}
}
},
blockedDirections = listOf(Direction.Up, Direction.Down)
),
colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colors.surface),
shape = RoundedCornerShape(16.dp)
) {
Box(modifier = Modifier.fillMaxSize()) {
PostCardInfoUi(
post = post,
onDescriptionClicked = onDescriptionClicked,
onOwnProfileClicked = onOwnProfileClicked,
onOtherProfileClicked = onOtherProfileClicked,
pagerState = pagerState
)
ReactionOverlay(
modifier = Modifier.fillMaxSize(),
isLike = swipeState.offset.value.x > 0,
alpha = (absoluteOffsetDp.value / 100).coerceAtLeast(0f)
)
}
}
Код: Выделить всё
fun Modifier.swipableCard(
state: SwipeableCardState,
onSwiped: (Direction) -> Unit,
onSwipeCancel: () -> Unit = {},
blockedDirections: List = listOf(Direction.Up, Direction.Down),
) = pointerInput(key1 = pagerPage, key2 = pagerCoordsRect) {
coroutineScope {
detectDragGestures(
onDragCancel = {
launch {
state.reset()
onSwipeCancel()
}
},
onDrag = { change, dragAmount ->
launch {
Log.i("SwipableCard", "Drag amount: $dragAmount")
val original = state.offset.targetValue
val summed = original + dragAmount
val newValue = Offset(
x = summed.x.coerceIn(-state.maxWidth, state.maxWidth),
y = summed.y.coerceIn(-state.maxHeight, state.maxHeight)
)
if (change.positionChange() != Offset.Zero) change.consume()
state.drag(newValue.x, newValue.y)
}
},
onDragEnd = {
launch {
val coercedOffset = state.offset.targetValue
.coerceIn(
blockedDirections,
maxHeight = state.maxHeight,
maxWidth = state.maxWidth
)
if (hasNotTravelledEnough(state, coercedOffset)) {
state.reset()
onSwipeCancel()
} else {
val horizontalTravel = abs(state.offset.targetValue.x)
val verticalTravel = abs(state.offset.targetValue.y)
if (horizontalTravel > verticalTravel) {
if (state.offset.targetValue.x > 0) {
state.swipe(Direction.Right)
onSwiped(Direction.Right)
} else {
state.swipe(Direction.Left)
onSwiped(Direction.Left)
}
} else {
if (state.offset.targetValue.y < 0) {
state.swipe(Direction.Up)
onSwiped(Direction.Up)
} else {
state.swipe(Direction.Down)
onSwiped(Direction.Down)
}
}
}
}
},
)
}
}.graphicsLayer {
translationX = state.offset.value.x
translationY = state.offset.value.y
rotationZ = (state.offset.value.x / 60).coerceIn(-40f, 40f)
}
private fun Offset.coerceIn(
blockedDirections: List,
maxHeight: Float,
maxWidth: Float,
): Offset {
return copy(
x = x.coerceIn(
if (blockedDirections.contains(Direction.Left)) {
0f
} else {
-maxWidth
},
if (blockedDirections.contains(Direction.Right)) {
0f
} else {
maxWidth
}
),
y = y.coerceIn(
if (blockedDirections.contains(Direction.Up)) {
0f
} else {
-maxHeight
},
if (blockedDirections.contains(Direction.Down)) {
0f
} else {
maxHeight
}
)
)
}
private fun hasNotTravelledEnough(
state: SwipeableCardState,
offset: Offset,
): Boolean {
return abs(offset.x) < state.maxWidth / 4 &&
abs(offset.y) < state.maxHeight / 4
}
enum class Direction {
Left, Right, Up, Down
}
@Composable
fun rememberSwipeableCardState(): SwipeableCardState {
val screenWidth = with(LocalDensity.current) {
LocalConfiguration.current.screenWidthDp.dp.toPx()
}
val screenHeight = with(LocalDensity.current) {
LocalConfiguration.current.screenHeightDp.dp.toPx()
}
return remember {
SwipeableCardState(screenWidth, screenHeight)
}
}
class SwipeableCardState(
internal val maxWidth: Float,
internal val maxHeight: Float,
) {
val offset = Animatable(offset(0f, 0f), Offset.VectorConverter)
/**
* The [Direction] the card was swiped at.
*
* Null value means the card has not been swiped fully yet.
*/
var swipedDirection: Direction? by mutableStateOf(null)
private set
internal suspend fun reset() {
offset.animateTo(offset(0f, 0f), tween(400))
}
suspend fun swipe(direction: Direction, animationSpec: AnimationSpec = tween(400)) {
val endX = maxWidth * 1.5f
val endY = maxHeight
when (direction) {
Direction.Left -> offset.animateTo(offset(x = -endX), animationSpec)
Direction.Right -> offset.animateTo(offset(x = endX), animationSpec)
Direction.Up -> offset.animateTo(offset(y = -endY), animationSpec)
Direction.Down -> offset.animateTo(offset(y = endY), animationSpec)
}
this.swipedDirection = direction
}
private fun offset(x: Float = offset.value.x, y: Float = offset.value.y): Offset {
return Offset(x, y)
}
internal suspend fun drag(x: Float, y: Float) {
offset.animateTo(offset(x, y))
}
}
Я пытался написать свою собственную функцию расширения defineDragGestures и передать ей координаты HorizontalPager с текущим состоянием страницы (первое, среднее, последнее), но не смог этого сделать. Если я использую ввод указателя awaitFirstDown(), пейджер не получит его и не переместится. Если я подожду, пока touchSlop перейдет к решить, что делать (провести пейджер или провести карточкой) на основе направления positionChange() и координат position, горизонтальный пейджер использует указатель awaitFirstDown() и перетаскивания карты не происходит.
-
Подробнее здесь: https://stackoverflow.com/questions/789 ... ontalpager
Мобильная версия