コード
suspend fun PointerInputScope.detectTapOrLongPressingGestures(
onTap: () -> Unit,
onLongPressing: () -> Unit,
longPressInterval: Long = 100L
) = coroutineScope {
awaitEachGesture {
val down = awaitFirstDown()
val longPressChange = awaitLongPressOrCancellation(down.id)
if (longPressChange == null) {
onTap()
return@awaitEachGesture
}
launch {
do {
delay(longPressInterval)
onLongPressing()
} while (!longPressChange.isConsumed)
}
waitForUpOrCancellation()
longPressChange.consume()
}
}
連続タップは onTap の連続にしている。
2本指は、両方の指が完全にリリースされればLongPress終了。2本指でもLongPressに満たない間隔で両方の指をリリースすればタップにしている。
使ったもの
PointerInputScope
fun Modifier.pointerInput(key1: Any?, block: suspend PointerInputScope.() -> Unit): Modifier
ポインタイベントが発生中はそのイベントに対する処理を行い、イベントが発生してない時は 次のイベントが発生するまで中断されるスコープ。これをループすることで イベントの待ち受けをやっている。
PointerInputScope.*()
PointerInputScope には gesture を検出するための拡張関数がいくつかある。
PointerInputScope.detectTapGestures(
onDoubleTap: ((Offset) -> Unit)?,
onLongPress: ((Offset) -> Unit)?,
onPress: suspend PressGestureScope.(Offset) -> Unit,
onTap: ((Offset) -> Unit)?
)
これのonLongPress は 、一度長押しを消費すると 指を離すまで次のポインタイベントは 処理されないので 、今回は使えない。
PointerInputScope.awaitEachGesture(block: suspend AwaitPointerEventScope.() -> Unit)
Gestureを細かく制御するならこっち
AwaitPointerEventScope
そのままだが、ポインタイベントを待ち受けるためのスコープ。
そのための拡張関数が用意されている。
AwaitPointerEventScope.awaitFirstDown(
requireUnconsumed: Boolean,
pass: PointerEventPass
)
最初に、指が Downされるまで 待ち受け
AwaitPointerEventScope.awaitLongPressOrCancellation(
pointerId: PointerId
)
ダブルタップを検出するまで待ち受け
AwaitPointerEventScope.waitForUpOrCancellation(
pass: PointerEventPass
)
指が Upされるまで待ち受け。2本以上の指によるGestureは全てがUpされるまで待ち受け。
PointerInputChange
await*
関数は PointerInputChange
を返す。
これはその時点での指の位置や押されてるかどうか といった Pointerイベントの状態。
val longPressChange = awaitLongPressOrCancellation(down.id)
長押しが検出される前にすべてのポインタが上げられた場合、または他のジェスチャが変更を消費した場合はNULL
キャンセル と ほかGesture による消費 を見分けたい時はどうすればいいんだろう..
今回はnull であればシングルタップとみなしている。
処理をコメントしていく
suspend fun PointerInputScope.detectTapOrLongPressingGestures(
onTap: () -> Unit,
onLongPressing: () -> Unit,
longPressInterval: Long = 100L
) = coroutineScope {
awaitEachGesture {
// 最初のDownイベントを待ち受け
val down = awaitFirstDown()
// 上のdownイベントの長押しを待ち受け
val longPressChange = awaitLongPressOrCancellation(down.id)
// 長押しでなければシングルタップ
if (longPressChange == null) {
onTap()
return@awaitEachGesture
}
launch {
// 長押し が消費される(指が離される)まで 長押しイベントを上げる
do {
delay(longPressInterval)
onLongPressing()
} while (!longPressChange.isConsumed)
}
// 指が離れるのを待ち受け
waitForUpOrCancellation()
// 指が離れたら、長押しを消費して 上のループを抜ける
longPressChange.consume()
}
}