zt
06/18/2022, 11:30 PMdetectDragGesturesAfterLongPress
where I'm able to handle the gesture differently depending if its horizontal or vertical? there's handlers for those directions but neither of them are after a long presstad
06/19/2022, 7:49 PM/**
* Gesture detector that waits for pointer down, then a long press, then any horizontal movement.
*
* @param onDragStart Called when a long press is detected.
* @param onDragEnd Called after all pointers are up.
* @param onDragCancel Called after another gesture has consumed pointer input.
* @param onHorizontalDrag Called for each drag event with the last known pointer position.
*/
suspend fun PointerInputScope.detectHorizontalDragGesturesAfterLongPress(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onHorizontalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit
) {
forEachGesture {
val down = awaitPointerEventScope {
awaitFirstDown(requireUnconsumed = false)
}
try {
val drag = awaitLongPressOrCancellation(down)
if (drag != null) {
onDragStart.invoke(drag.position)
awaitPointerEventScope {
if (
horizontalDrag(drag.id) {
onHorizontalDrag(it, it.positionChange().x)
it.consumePositionChange()
}
) {
// Consume up event if we quit the drag.
currentEvent.changes.fastForEach {
if (it.changedToUp()) {
it.consumeDownChange()
}
}
onDragEnd()
} else {
onDragCancel()
}
}
}
} catch (c: CancellationException) {
onDragCancel()
throw c
}
}
}
private suspend fun PointerInputScope.awaitLongPressOrCancellation(
initialDown: PointerInputChange
): PointerInputChange? {
var longPress: PointerInputChange? = null
var currentDown = initialDown
val longPressTimeout = viewConfiguration.longPressTimeoutMillis
return try {
// wait for first tap up or long press
withTimeout(longPressTimeout) {
awaitPointerEventScope {
var finished = false
while (!finished) {
val event = awaitPointerEvent(PointerEventPass.Main)
if (event.changes.fastAll { it.changedToUpIgnoreConsumed() }) {
// All pointers are up
finished = true
}
if (
event.changes.fastAny {
it.consumed.downChange || it.isOutOfBounds(size, extendedTouchPadding)
}
) {
finished = true // Canceled
}
// Check for cancel by position consumption. We can look on the Final pass of
// the existing pointer event because it comes after the Main pass we checked
// above.
val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
if (consumeCheck.changes.fastAny { it.positionChangeConsumed() }) {
finished = true
}
if (!event.isPointerUp(currentDown.id)) {
longPress = event.changes.fastFirstOrNull { it.id == currentDown.id }
} else {
val newPressed = event.changes.fastFirstOrNull { it.pressed }
if (newPressed != null) {
currentDown = newPressed
longPress = currentDown
} else {
// should technically never happen as we checked it above
finished = true
}
}
}
}
}
null
} catch (_: TimeoutCancellationException) {
longPress ?: initialDown
}
}
private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean =
changes.fastFirstOrNull { it.id == pointerId }?.pressed != true
tad
06/19/2022, 7:51 PMonVerticalDrag
callbacktad
06/19/2022, 7:52 PMawaitLongPressOrCancellation
is copied from compose-ui because it's internal therezt
06/19/2022, 11:47 PM