Есть несколько вещей, которые мы должны учитывать при работе с сенсорными событиями и анимациями, по сравнению с тем, когда мы работаем только с анимацией. Во-первых, нам может потребоваться прервать текущую анимацию, когда начинаются сенсорные события, поскольку взаимодействие с пользователем должно иметь наивысший приоритет.
В примере ниже мы используем 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
composable). Горизонтальное смещение элемента представлено как 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 отключен.
- Ценностно-ориентированная анимация
- Перетаскивайте, проводите и бросайте
- Понимать жесты