タップイベントやアニメーションを使用する場合は、アニメーションだけを使用する場合と比べて、考慮すべき点がいくつかあります。まず、ユーザー操作を最優先する必要があるため、場合によっては、タップイベントの開始時に実行中のアニメーションを中断する必要があります。
以下の例では、Animatable
を使用して円コンポーネントのオフセット位置を表しています。タップイベントは、pointerInput
修飾子で処理されます。新しいタップイベントを検出すると、animateTo
を呼び出して、タップ位置までのオフセット値をアニメーション化します。アニメーション中にタップイベントが発生する場合もあります。その場合、animateTo
は実行中のアニメーションを中断し、中断されたアニメーションの速度を維持しながら、新しいターゲット位置までのアニメーションを開始します。
@Composable fun Gesture() { val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { coroutineScope { while (true) { // Detect a tap event and obtain its position. awaitPointerEventScope { val position = awaitFirstDown().position launch { // Animate to the tap position. offset.animateTo(position) } } } } } ) { Circle(modifier = Modifier.offset { offset.value.toIntOffset() }) } } private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())
もう 1 つのよくあるパターンでは、アニメーション値を、ドラッグなどのタップイベントから取得した値と同期させる必要があります。以下の例では、SwipeToDismiss
コンポーザブルを使用するのではなく、Modifier
として「スワイプで閉じる」を実装しています。要素の水平オフセットは、Animatable
として表されます。この API には操作アニメーションで役立つ特性があります。この値は、タップイベントとアニメーションによって変更できます。タップイベントを受け取ると、進行中のすべてのアニメーションがインターセプトされるように、stop
メソッドにより Animatable
を停止します。
ドラッグ イベントでは、snapTo
を使用して、タップイベントから計算された値で Animatable
の値を更新します。フリングについては、Compose に用意されている VelocityTracker
を使うと、ドラッグ イベントを記録して速度を計算できます。速度は animateDecay
に直接送られ、フリング アニメーションに使用されます。オフセット値を元の位置に戻すには、animateTo
メソッドでターゲット オフセット値を 0f
に指定します。
fun Modifier.swipeToDismiss( onDismissed: () -> Unit ): Modifier = composed { val offsetX = remember { Animatable(0f) } pointerInput(Unit) { // Used to calculate fling decay. val decay = splineBasedDecay<Float>(this) // Use suspend functions for touch events and the Animatable. coroutineScope { while (true) { val velocityTracker = VelocityTracker() // Stop any ongoing animation. offsetX.stop() awaitPointerEventScope { // Detect a touch down event. val pointerId = awaitFirstDown().id horizontalDrag(pointerId) { change -> // Update the animation value with touch events. launch { offsetX.snapTo( offsetX.value + change.positionChange().x ) } velocityTracker.addPosition( change.uptimeMillis, change.position ) } } // No longer receiving touch events. Prepare the animation. val velocity = velocityTracker.calculateVelocity().x val targetOffsetX = decay.calculateTargetValue( offsetX.value, velocity ) // The animation stops when it reaches the bounds. offsetX.updateBounds( lowerBound = -size.width.toFloat(), upperBound = size.width.toFloat() ) launch { if (targetOffsetX.absoluteValue <= size.width) { // Not enough velocity; Slide back. offsetX.animateTo( targetValue = 0f, initialVelocity = velocity ) } else { // The element was swiped away. offsetX.animateDecay(velocity, decay) onDismissed() } } } } } .offset { IntOffset(offsetX.value.roundToInt(), 0) } }
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- 価値ベースのアニメーション
- ドラッグ、スワイプ、フリング
- ジェスチャーについて