Bei der Arbeit mit Touch-Ereignissen und Animationen müssen wir mehrere Dinge beachten, die bei der Arbeit mit reinen Animationen nicht relevant sind. Erstens: Möglicherweise müssen wir eine laufende Animation unterbrechen, wenn Touch-Ereignisse beginnen, da die Nutzerinteraktion die höchste Priorität haben sollte.
Im folgenden Beispiel wird mit einem Animatable
die Offset-Position einer Kreiskomponente dargestellt. Touch-Ereignisse werden mit dem Modifikator pointerInput
verarbeitet. Wenn wir ein neues Tippen-Ereignis erkennen, wird animateTo
aufgerufen, um den Offset-Wert an die Tippposition zu animieren. Ein Tipp-Ereignis kann auch während der Animation auftreten. In diesem Fall unterbricht animateTo
die laufende Animation und startet die Animation zur neuen Zielposition, 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 aus Touch-Ereignissen synchronisieren müssen, z. B. Ziehen. Im folgenden Beispiel wird „Wischen zum Schließen“ als Modifier
implementiert, anstatt die SwipeToDismiss
-Komposition zu verwenden. Der horizontale Offset des Elements wird als Animatable
dargestellt. Diese API hat eine Eigenschaft, die sich für die Gestenanimation eignet. Der Wert kann durch Touch-Ereignisse und die Animation geändert werden. Wenn wir ein Touchdown-Ereignis erhalten, stoppen wir die Animatable
mit der Methode stop
, damit laufende Animationen abgefangen werden.
Während eines Drag-Ereignisses wird mit snapTo
der Wert Animatable
mit dem Wert aktualisiert, der aus Touch-Ereignissen berechnet wird. Für Wischaktionen bietet Compose VelocityTracker
, um Ziegereignisse aufzuzeichnen und die Geschwindigkeit zu berechnen. Die Geschwindigkeit kann direkt an animateDecay
für die Wischanimation übergeben werden. Wenn wir den Offsetwert wieder in die ursprüngliche Position schieben möchten, geben wir mit der Methode animateTo
den Zieloffsetwert 0f
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 schleudern
- Gesten