Composeの様々なクリック処理

Kenji Abe
6 min readJan 20, 2023

ComposeではModifierを使ってクリックを処理する方法がいくつかありますので、それらをまとめておきます。

Modifier.clickable

clickable は一番シンプルなクリックイベントを処理するものになります。

Box(
modifier = Modifier.clickable {
// クリックされたときの処理
}
)

この clickable を適用すると自動的にクリック時にRipple Effectも行われるようになります。

Modifier.combinedClickable

単純なダブルクリックやロングクリックを処理する場合は combinedClickable を使うことで簡単に実装できます。

Box(
modifier = Modifier
.combinedClickable(
onClick = {
// クリック
},
onDoubleClick = {
// ダブルクリック
},
onLongClick = {
// ロングクリック
}
)
)

こちらも clickable と同様に自動でRipple Effectが行われるようになります。

注意としてはこれを書いてる時点ではExperimentalになっているため、今後変更される構成があります。

Modifier.pointerInput

最後に少し特殊な pointerInput です。こちらはGesture全般を扱うことができます。今回は detectTapGestures を使った処理を紹介します。他にも、 detectDragGestures などもあります。

Box(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
},
onLongPress = {
},
onPress = {
},
onTap = {
}
)
},
)

pointerInput 内で detectTapGestures 関数の各引数でイベントを受け取ることができるようになっています。clickable とは異なりこちらのほうは自動でRipple Effectを行われません。

また各イベントでは Offset を受け取ることができるので、押された箇所がわかるようになっています。
この Offset 値はマイナス値や要素サイズより大きい値になる場合があります。(後述)

detectTapGestures(
onTap = {
val x = it.x
val y = it.y
},
)

さらに onPress では押してる状態から離すまでを検知することができます。これを使って押してる間だけ表示を変えるようなことも可能です。

var pressing by remember { mutableStateOf(false) }

Box(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = {
// 押してる
pressing = true

tryAwaitRelease()
// 離した

pressing = false
},
)
},
)

押される間は tryAwaitRelease で待つことになり、離されたときに後続の処理を実行します。また、戻り値でtrue/falseを返しますが、領域内で指を離したときはtrueを返しますが、押したまま領域の外に移動するとfalseを返します。
細かい制御ができるようになっています。

ViewConfigurationを使ったカスマイズ

ダブルクリックとして検知する間隔や、どれくらいの時間押していればダブルクリックと検知されるか、などを ViewConfiguration を使ってカスマイズできるようになっています。

ViewConfiguration を実装したクラスを作成して、それを CompositionLocal を使って設定する感じなります。サンプルとしてデフォルト設定を活かしつつロングクリックと判定される時間を設定します。

// カスマイズしたViewConfiguration
class CustomViewConfiguration(
private val defaultViewConfiguration: ViewConfiguration // デフォルト
) : ViewConfiguration by defaultViewConfiguration {

// 3000ms押し続ければロングクリックとして判定
override val longPressTimeoutMillis: Long = 3000
}

@Composable
fun Sample() {
// デフォルトのViewConfigurationを取得
val defaultViewConfiguration = LocalViewConfiguration.current

val viewConfiguration = remember {
CustomViewConfiguration(defaultViewConfiguration)
}

CompositionLocalProvider(
LocalViewConfiguration provides viewConfiguration
) {
// ここの中はカスマイズされた値が使用される

Box(
modifier = Modifier
.combinedClickable(
onLongClick = {
// ...
},
)
)
}
}

また、 ViewConfiguration では、最小のタップエリアも定義してます。このタップエリアより小さいサイズの要素は、 pointerInput の処理は最小サイズの範囲で反応します。そのため、実際描画されてるエリアより広い範囲でタップイベントを検知することがあるので、 pointerInput の処理で取得できるOffsetはマイナス値や要素サイズより大きい場合があります。

デフォルトでは48.dpになっているので、これを0.dpにすることで要素サイズが小さい場合でも、マイナス値や要素サイズを超えたOffset値は受け取らなくなります。

class CustomViewConfiguration(
private val defaultViewConfiguration: ViewConfiguration
) : ViewConfiguration by defaultViewConfiguration {

override val minimumTouchTargetSize: DpSize = DpSize(0.dp, 0.dp)
}

ViewConfiguration の設定は、 combinedClickablepointerInput も影響を受けます。

参考

--

--

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO