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