AndroidのJetpack Composeを使用しているとき、ダブルタップや複数のUIを同時タップされたときに一つのイベントしか処理したくない時とかありますよね。
そんな時はModifier.clickable
を拡張した次のような関数を定義することが多いと思います。
ClickableOnceForJetpackCompose.kt (syarihuさんのコードを引用)
開発初期など手戻りコストが少ない時は私も迷わず同じような実装をするかと思いますが、画面をたくさん作り終わってしまった時や、画面全体でマルチタップ(ダブルタップや複数のUI同時タップ)を防止したい時の代替案はないかなーと考えていた時に作ってみました。
以下のような拡張関数を定義して親ComposableのModifierに指定する形です。
/*
マルチタップを感知した時、子Composableへのイベントの伝搬をキャンセルする
*/
fun Modifier.interceptChildrenMultiTaps(): Modifier = this.then(
Modifier.pointerInput(Unit) {
awaitPointerEventScope {
// 前回のUPイベントがあった時刻
var prevUpTimeMills: Long
// 最新のUPイベントがあった時刻
var upTimeMillis = 0L
while (true) {
// PointerEventPass.Initialイベントが発生するのを待機
val passInitial = awaitPointerEvent(pass = PointerEventPass.Initial)
val event = passInitial.changes.first()
// 押下イベント判定
when (event.pressed) {
true -> {}
else -> {
// PointerEventがあった時刻の値を更新
prevUpTimeMills = upTimeMillis
upTimeMillis = event.uptimeMillis
// 前回と今回の押下イベントの時刻の差分を計算
val delta = upTimeMillis - prevUpTimeMills
if (delta <= viewConfiguration.doubleTapTimeoutMillis) {
// タップイベント間の時刻の差分が規定値以下であれば、イベントをconsumeして子Composableへの伝搬を止める
event.consumeDown()
Log.d("touchTest", "double-tap detected, and event intercepted.")
}
}
}
}
}
}
)
fun PointerInputChange.consumeDown() {
if (pressed != previousPressed) {
consume()
}
}
}
実際に使用する場合は以下のようになります。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
//親Composableに指定してマルチタップを感知したら子Composableへのイベント伝搬を止める
.interceptChildrenMultiTaps()
) {
Button(
onClick = { context.startActivity(Intent(context, SecondActivity::class.java)) },
) {
Text("GO TO SECOND")
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { context.startActivity(Intent(context, SecondActivity::class.java)) },
) {
Text("GO TO SECOND")
}
}
}
}
上記の実装をすると、Modifier.interceptChildrenMultiTaps
を指定したComposable配下のComposableの連続タップを防ぐことができます。また、上記の例ではButtonを2個配置していますが、これを同時押ししても1つのイベントしか走らないよう制御ができるようになっています。
これで今の所一応動いてはいますが、不具合を見つけたり、他にもっといい方法や改善点等ご存知の方がいたら、ぜひ教えていただきたいです。
また、徹底的に検証したわけではないので、使用の際は自己責任でお願いします。