Bei der Arbeit mit Touch-Ereignissen und Animationen müssen wir im Vergleich zur Arbeit mit Animationen allein einige Dinge berücksichtigen. Zuerst müssen wir möglicherweise eine laufende Animation unterbrechen, wenn Touch-Ereignisse beginnen, da Nutzerinteraktionen die höchste Priorität haben sollten.
Im folgenden Beispiel verwenden wir ein Animatable
, um die Offsetposition einer Kreiskomponente darzustellen. Touch-Ereignisse werden mit dem Modifikator pointerInput
verarbeitet. Wenn wir ein neues Tippereignis erkennen, rufen wir animateTo
auf, um den Offsetwert an die Tippposition zu animieren. Ein Tippereignis kann auch während der Animation auftreten. In diesem Fall wird die laufende Animation durch animateTo
unterbrochen und die Animation zur neuen Zielposition wird gestartet, wobei 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äufiges Muster ist, dass wir Animationswerte mit Werten synchronisieren müssen, die von Touch-Ereignissen wie Ziehen stammen. Im Beispiel unten sehen wir, dass „Zum Schließen wischen“ als Modifier
implementiert ist (anstatt die SwipeToDismiss
-Composable zu verwenden). Der horizontale Offset des Elements wird als Animatable
dargestellt. Diese API hat eine Eigenschaft, die für die Gestenanimation nützlich ist. Sein Wert kann durch Touch-Ereignisse und die Animation geändert werden. Wenn wir ein Touchdown-Ereignis empfangen, beenden wir Animatable
mit der Methode stop
, damit laufende Animationen abgefangen werden.
Während eines Drag-Vorgangs wird snapTo
verwendet, um den Animatable
-Wert mit dem aus Touch-Ereignissen berechneten Wert zu aktualisieren. Für Fling-Gesten bietet Compose VelocityTracker
, um Zieh-Ereignisse aufzuzeichnen und die Geschwindigkeit zu berechnen. Die Geschwindigkeit kann direkt an animateDecay
für die Fling-Animation übergeben werden. Wenn wir den Offsetwert wieder in die ursprüngliche Position verschieben möchten, geben wir den Ziel-Offsetwert 0f
mit der Methode animateTo
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: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Wertbezogene Animationen
- Ziehen, wischen und schleudern
- Gesten