Gdy pracujemy ze zdarzeniami dotyku i animacjami, trzeba wziąć pod uwagę kilka kwestii niż w przypadku samych animacji. Przede wszystkim konieczne może być przerwanie trwającej animacji po rozpoczęciu zdarzenia dotknięcia, ponieważ interakcja użytkownika powinna mieć najwyższy priorytet.
W przykładzie poniżej użyto elementu Animatable
do oznaczenia pozycji przesunięcia komponentu okręgu. Zdarzenia kliknięcia są przetwarzane za pomocą modyfikatora pointerInput
. Gdy wykryjemy nowe zdarzenie kliknięcia, wywołujemy funkcję animateTo
, aby animować wartość przesunięcia do pozycji kliknięcia. Dotknięcie może też mieć miejsce w trakcie animacji. W takim przypadku animateTo
przerywa trwającą animację i uruchamia ją do nowej pozycji docelowej, zachowując przy tym tempo przerywanej animacji.
@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())
Częstym wzorcem jest synchronizowanie wartości animacji z wartościami pochodzącymi ze zdarzeń dotknięcia, takich jak przeciąganie. W przykładzie poniżej widzimy działanie funkcji „przesuń, aby zamknąć” zaimplementowaną jako Modifier
(a nie za pomocą funkcji kompozycyjnej SwipeToDismiss
). Przesunięcie w poziomie elementu jest przedstawiane jako Animatable
. Ten interfejs API ma cechę przydatną w animacji gestów. Jej wartość można zmieniać za pomocą zdarzeń dotknięcia i animacji. Po otrzymaniu zdarzenia dotknięcia zatrzymujemy metodę Animatable
za pomocą metody stop
, aby umożliwić przechwycenie trwającej animacji.
W trakcie zdarzenia przeciągania używamy parametru snapTo
, aby zaktualizować wartość Animatable
o wartość obliczoną na podstawie zdarzeń dotknięcia. W przypadku przesuwania funkcja tworzenia udostępnia VelocityTracker
do rejestrowania zdarzeń przeciągania i obliczania prędkości. Prędkość można przesłać bezpośrednio do obiektu animateDecay
na potrzeby animacji rzutu. Gdy chcemy przesunąć wartość przesunięcia z powrotem do pozycji pierwotnej, określamy wartość przesunięcia docelowego 0f
za pomocą metody animateTo
.
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) } }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Animacje na podstawie wartości
- Przeciąganie, przesuwanie i przesuwanie
- Gesty