Wenn wir mit Touch-Ereignissen und -Animationen arbeiten, müssen wir einige Dinge beachten, wenn wir mit Animationen allein arbeiten. Zuerst muss möglicherweise eine laufende Animation unterbrochen werden, wenn Touchereignisse beginnen, da Nutzerinteraktionen die höchste Priorität haben sollten.
Im folgenden Beispiel verwenden wir ein Animatable
, um die Versatzposition einer Kreiskomponente darzustellen. Touch-Ereignisse werden mit dem Modifizierer pointerInput
verarbeitet. Wenn ein neues Tippereignis erkannt wird, rufen wir animateTo
auf, um den Versatzwert zur Tippposition zu animieren. Auch während der Animation kann ein Tippereignis auftreten. In diesem Fall unterbricht animateTo
die laufende Animation und startet die Animation an der neuen Zielposition, während die Geschwindigkeit der unterbrochenen Animation beibehalten wird.
@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())
Ein weiteres häufig auftretendes Muster ist die Synchronisierung von Animationswerten mit Werten, die aus Touch-Ereignissen wie dem Ziehen stammen. Im Beispiel unten wird „Zum Schließen wischen“ als Modifier
implementiert, anstatt die zusammensetzbare Funktion SwipeToDismiss
zu verwenden. Der horizontale Versatz des Elements wird als Animatable
dargestellt. Diese API hat ein Merkmal, das bei Gestenanimation nützlich ist. Der Wert kann durch Touch-Ereignisse und die Animation geändert werden. Wenn wir ein Touchdown-Ereignis erhalten, stoppen wir den Animatable
mit der Methode stop
, damit die laufende Animation abgefangen wird.
Während eines Drag-Ereignisses verwenden wir snapTo
, um den Animatable
-Wert mit dem aus Touch-Ereignissen berechneten Wert zu aktualisieren. Für das fling bietet Compose VelocityTracker
an, um Drag-Ereignisse aufzuzeichnen und die Geschwindigkeit zu berechnen. Die Geschwindigkeit kann für die Schleuder-Animation direkt an animateDecay
übergeben werden. Um den Offset-Wert zurück an die ursprüngliche Position zu verschieben, geben wir den Ziel-Offset-Wert 0f
mit der animateTo
-Methode an.
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) } }
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Wertbezogene Animationen
- Ziehen, wischen und schleppen
- Gesten und Bewegungen