Há diversos fatores que precisam ser considerados ao trabalhar com a combinação de eventos de toque e animações, em comparação com quando trabalhamos apenas com animações. Primeiro, talvez seja necessário interromper uma animação em andamento quando o evento de toque for iniciado, já que a interação do usuário tem maior prioridade.
No exemplo abaixo, usamos Animatable
para representar a posição de deslocamento de
um componente de círculo. Os eventos de toque são processados com o
modificador
pointerInput
. Ao detectar um novo evento de toque, animateTo
é chamado para animar o
valor de deslocamento para a posição de toque. Um evento de toque também pode acontecer durante a animação.
Nesse caso, animateTo
interrompe a animação em andamento e inicia
a animação na nova posição de destino, mantendo a velocidade da
animação interrompida.
@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())
Outro padrão frequente é precisar sincronizar os valores de animação com valores
originados de eventos de toque, como o recurso de arrastar. No exemplo abaixo, vemos o recurso "deslizar para
dispensar" implementado como um Modifier
, em vez de usar a função
SwipeToDismiss
. O deslocamento horizontal do elemento é representado como
Animatable
. Essa API tem uma característica útil na animação de gestos. O
valor dela pode ser mudado por eventos de toque e pela animação. Quando um evento de toque
é recebido, o Animatable
é interrompido pelo método stop
, para que qualquer
animação em andamento seja interceptada.
Durante um evento de arrastar, snapTo
é usado para atualizar o valor Animatable
com o
valor calculado de eventos de toque. Para a rolagem, o Compose
fornece o VelocityTracker
para gravar eventos de arrastar e calcular a velocidade. A velocidade pode ser
alimentada diretamente em animateDecay
para a animação com rolagem. Para mover
o valor de deslocamento de volta à posição original, o deslocamento de destino de
0f
precisa ser especificado com o método 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) } }
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Animações com base no valor
- Arrastar, deslizar e mover
- Noções básicas sobre gestos