🦔
JetpackCompose リップルエフェクトとアニメーションを併用したクリックイベントを実装する方法
こちらにて現在のコードに至った経緯とエラーケースを書いてますので
よければこちらもこちらも見てみて下さい。
やりたい事
- クリック、ロングクリック時にアニメーションとクリックイベント
- ホールドしてカーソルを外すとアニメーションを停止、クリックイベントを起こさない
まぁ良くあるボタンイベントである。
だけどJetpackComposeでこれを実装しようと思うと
本当によくわからなくて困り果てていました;;;;;;;;
理由
- 現状クリックイベントではクリック、ロングクリックには対応できるが、ボタンを離した時・ホールドしてカーソルを外した時などの細かい出し分けには対応していない
- 細かい出し分けを行うにはpointerInteropFilterを使ってMotionEventによる出し分けをする必要がある
- しかしpointerInteropFilterを使うとリップルエフェクトを実装する方法がない
- 空のclickableを作ってそこにリップルエフェクトを定義してpointerInteropFilterを付けpointerInteropFilterのBooleanをfalseにすると、リップルエフェクトは付くがスケールしたアニメーションにリップルエフェクトが乗らなかったり、pointerInteropFilterのBooleanをfalseになっているので最初のクリックイベントで監視が終了してしまって出し分けに対応できなかったりする
結論&サンプルコード
fun Modifier.animationClickable(
interactionSource: MutableInteractionSource,
onClick: () -> Unit,
enabled: Boolean = true
): Modifier = composed {
val onClickState = rememberUpdatedState(onClick)
var playedAnimation by remember {
mutableStateOf(false)
}
var f by remember {
mutableStateOf(1.0f)
}
val scale by
animateFloatAsState(
targetValue = if (playedAnimation) 0.9f else f,
animationSpec = repeatable(
iterations = 2,
animation = tween(durationMillis = 300),
),
)
scale(scale)
.pointerInput(interactionSource, enabled) {
if (enabled) {
forEachGesture {
coroutineScope {
awaitPointerEventScope {
val down = awaitFirstDown(requireUnconsumed = false)
val downPress = PressInteraction.Press(down.position)
val holdButtonJob = launch {
interactionSource.emit(downPress)
f = 0.9f
playedAnimation = true
}
val up = waitForUpOrCancellation()
holdButtonJob.cancel()
launch {
when (up) {
null -> {
interactionSource.emit(PressInteraction.Cancel(downPress))
}
else -> {
interactionSource.emit(PressInteraction.Release(downPress))
onClickState.value.invoke()
}
}
f = 1.0f
playedAnimation = false
}
}
}
}
}
}
.indication(interactionSource, rememberRipple())
}
解説
pointerInteropFilterではなくpointerInputを使う。
pointerInputだとkeyを渡すことができるので
そこにinteractionSourceを渡し、
各アクションをinteractionSourceにemitすることと
pointerInputに.indication()を付けることで
リップルエフェクトを実装することができる。
リップルエフェクトがスケールしないのはModifierに記述する順序で動作が変わるためである。
アニメーションが起こる前にscaleを付けてあげることで追従することができる。
参考記事
Discussion