JetpackComposeのImageViewでジェスチャーとダブルタップを共存させる方法
やりたいこと
JetpackComposeのImageViewでジェスチャーでZoomInやZoomOutしつつ、ダブルタップした場合でも同様にZoomInやZoomOutしたかったけど、ちょっとハマったのでメモ。
ジェスチャー検知
ImageViewのジェスチャーはModifier.pointerInputのラムダ内で、 PointerInputScope.detectTransformGesturesを利用します。
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
Image(
painter = painterResource(id = R.drawable.sample),
contentDescription = "sample",
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
+ detectTransformGestures { _, pan, zoom, _ ->
+ scale *= zoom
+ offset += pan
+ }
}
.graphicsLayer {
// ズーム倍率変更
scaleX = scale
scaleY = scale
// 画像の移動
translationX = offset.x
translationY = offset.y
}
)
ダブルタップ検知
ImageViewのダブルタップは同じくModifier.pointerInputのラムダ内でPointerInputScope.detectTapGesturesを利用します。
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
Image(
painter = painterResource(id = R.drawable.sample),
contentDescription = "sample",
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
+ detectTapGestures {
+ onDoubleTap = {
+ // ダブルタップ時の処理
+ }
+ }
}
.graphicsLayer {
// ズーム倍率変更
scaleX = scale
scaleY = scale
// 画像の移動
translationX = offset.x
translationY = offset.y
}
)
ジェスチャーとダブルタップを検知する
なるほどなるほど。
じゃあ両方を同時に実装する場合は「detectTransformGestures」と「detectTapGestures」を書けば良さそう。
と言うことで以下のようにしたのですが、ダブルタップが反応せず。。。
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
Image(
painter = painterResource(id = R.drawable.sample),
contentDescription = "sample",
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
+ detectTransformGestures { _, pan, zoom, _ ->
+ scale *= zoom
+ offset += pan
+ }
+ detectTapGestures {
+ onDoubleTap = {
+ // ダブルタップ時の処理
+ }
+ }
}
.graphicsLayer {
// ズーム倍率変更
scaleX = scale
scaleY = scale
// 画像の移動
translationX = offset.x
translationY = offset.y
}
)
調べていくと、PointerInputではポインターが1つを考慮するだけなので、detectTapGesturesとdetectTransformGesturesを一緒に使用することはできないらしい。
Modifier.combinedClickable
代わりにModifier.combinedClickableのonDoubleClick()ラムダ内で処理を書けばジェスチャーとダブルタップが共存できた。(但し、@ExperimentalFoundationApiなので注意が必要・・・)
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
Image(
painter = painterResource(id = R.drawable.sample),
contentDescription = "sample",
contentScale = ContentScale.Fit,
modifier = Modifier
+ .combinedClickable(
+ onClick = {}, // onClickは初期値未設定なので必須
+ onDoubleClick = {
+ // ダブルタップ時の処理
+ }
+ )
.fillMaxSize()
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scale *= zoom
offset += pan
}
}
.graphicsLayer {
// ズーム倍率変更
scaleX = scale
scaleY = scale
// 画像の移動
translationX = offset.x
translationY = offset.y
}
)
ちなみに、Modifier.combinedClickableも中を見ると、detectTapGesturesを使ってるんですね。
自前でdetectTransformGesturesとdetectTapGesturesを実装するとダメな理由はなんだろう・・・。
最後に
ちなみに、素のcombinedClickable()を使うとIndicationにLocalIndication.currentが設定されてるのでリップルエフェクトが動く。
自分の場合は不要だったのでModifier.combinedClickable
にindicationを追加してデフォルトはリップルエフェクトを無効にしました。
@OptIn(ExperimentalFoundationApi::class)
fun Modifier.combinedClickable(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onLongClickLabel: String? = null,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
onClick: () -> Unit,
indication: Indication? = null
) = composed(
inspectorInfo = debugInspectorInfo {
name = "combinedClickable"
properties["enabled"] = enabled
properties["onClickLabel"] = onClickLabel
properties["role"] = role
properties["onClick"] = onClick
properties["onDoubleClick"] = onDoubleClick
properties["onLongClick"] = onLongClick
properties["onLongClickLabel"] = onLongClickLabel
}
) {
Modifier.combinedClickable(
enabled = enabled,
onClickLabel = onClickLabel,
onLongClickLabel = onLongClickLabel,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
onClick = onClick,
role = role,
indication = indication,
interactionSource = remember { MutableInteractionSource() }
)
}
追記:
Modifier.combinedClickableでindicationが引数にあるものもあったので、そちらを使えば自前でextensionしなくてよかったです。
参考記事
Discussion
完全に勘違いしてました。
pointerInput(Unit)を2つ書いてそれぞれにdetectTransformGesturesとdetectTapGesturesを実装してやれば普通にジェスチャーとダブルタップが共存できた!
あくまで1つのpointerInput(Unit)内にdetectTransformGesturesとdetectTapGesturesを実装しちゃいけないって話ですね。