Есть несколько вещей, которые мы должны учитывать, когда работаем с сенсорными событиями и анимацией, по сравнению с тем, когда мы работаем только с анимацией. Прежде всего, нам может потребоваться прервать текущую анимацию, когда начинаются события касания, поскольку взаимодействие с пользователем должно иметь наивысший приоритет.
В приведенном ниже примере мы используем Animatable
для представления положения смещения компонента круга. События касания обрабатываются с помощью модификатора pointerInput
. Когда мы обнаруживаем новое событие касания, мы вызываем animateTo
чтобы анимировать значение смещения в положении касания. Событие касания также может произойти во время анимации, и в этом случае animateTo
прерывает текущую анимацию и запускает анимацию в новую целевую позицию, сохраняя при этом скорость прерванной анимации.
@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())
Другой распространенный шаблон: нам необходимо синхронизировать значения анимации со значениями, поступающими от событий касания, таких как перетаскивание. В приведенном ниже примере мы видим, как «проведите пальцем по экрану, чтобы отклонить», реализованное как Modifier
(вместо использования составного элемента SwipeToDismiss
). Горизонтальное смещение элемента представлено как Animatable
. Этот API имеет особенность, полезную для анимации жестов. Его значение можно изменить с помощью событий касания, а также анимации. Когда мы получаем событие приземления, мы останавливаем Animatable
с помощью метода stop
, чтобы любая текущая анимация была перехвачена.
Во время события перетаскивания мы используем snapTo
чтобы обновить значение Animatable
значением, рассчитанным на основе событий касания. Для броска Compose предоставляет VelocityTracker
для записи событий перетаскивания и расчета скорости. Скорость можно передать непосредственно в animateDecay
для анимации полета. Когда мы хотим сдвинуть значение смещения обратно в исходное положение, мы указываем целевое значение смещения 0f
с помощью метода 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) } }
Рекомендуется для вас
- Примечание. Текст ссылки отображается, когда JavaScript отключен.
- Анимация на основе значений
- Перетащите, проведите пальцем по экрану и бросьте
- Понимание жестов