Реализация жестовых анимаций (Interactive Gesture Animations) в мобильном приложении
Разница между жестовой анимацией и обычной в том, что она прерываема. Пользователь тянет карточку, передумывает на середине, и она должна вернуться обратно с физически достоверным поведением — не просто прыгнуть в исходное положение, а «отпружинить» с учётом накопленной скорости. Это и есть interactive gesture animation — анимация, управляемая пальцем в реальном времени.
Физика — основа правильного ощущения
Ключевой параметр любой жестовой анимации — связь между скоростью жеста в момент отпускания (gestureRecognizer.velocity) и параметрами анимации завершения. Если их не синхронизировать, получается «прыжок»: палец движется с одной скоростью, а объект заканчивает движение с другой.
На iOS UIViewPropertyAnimator решает это нативно через continueAnimation(withTimingParameters:durationFactor:):
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
case .changed:
animator?.fractionComplete = translation.y / totalDistance
case .ended:
let velocity = gesture.velocity(in: view)
let springVelocity = abs(velocity.y) / (totalDistance - translation.y)
let timing = UISpringTimingParameters(dampingRatio: 0.8,
initialVelocity: CGVector(dx: 0, dy: springVelocity))
animator?.continueAnimation(withTimingParameters: timing, durationFactor: 0)
default: break
}
}
fractionComplete — это прямое управление прогрессом анимации жестом. Значение 0.0 — начало, 1.0 — конец. Пользователь буквально «тащит» анимацию за собой.
Свайп для отклонения карточки (card dismissal)
Классический паттерн в стиле Tinder или списков задач: карточка следует за пальцем, при достижении порога — улетает, при недостаточном смещении — возвращается.
Три параметра, которые нужно откалибровать:
- Порог отпускания — обычно 30-40% ширины экрана или скорость > 800 pt/s
-
Угол вращения —
transform = CGAffineTransform(rotationAngle: translation.x / screenWidth * 0.3)— создаёт ощущение физики - Скорость анимации улёта — должна соответствовать скорости жеста, иначе карточка «тормозит» в воздухе
На Android реализуем через ViewDragHelper или ItemTouchHelper (для RecyclerView). ItemTouchHelper хорош для списков, но ограничен: кастомный feedback рисунок требует переопределения onChildDraw.
В Flutter — Dismissible для простых случаев, GestureDetector + AnimationController с fling() для сложных. fling() принимает velocity напрямую из DragEndDetails.velocity.pixelsPerSecond — это именно та синхронизация, о которой говорим.
Pull-to-refresh с кастомной анимацией
Стандартный UIRefreshControl и SwipeRefreshLayout не поддерживают кастомную анимацию индикатора. Реализуем через ScrollView с отрицательным offset: когда contentOffset.y < -threshold, включаем кастомный индикатор с параметром прогресса = abs(offset.y) / threshold. Lottie-анимация, привязанная к setProgress(), даёт произвольный дизайн.
Drag & Drop с физическим поведением
На iOS — UIDragInteraction + UIDropInteraction для cross-app DnD, UILongPressGestureRecognizer + UIViewPropertyAnimator для внутриэкранного. Важно: при поднятии элемента добавляем scale-анимацию (1.05) и тень — визуальный сигнал «я держу объект». При возврате — UISpringTimingParameters с высоким dampingRatio (0.9) для эффекта «мягкой посадки».
Тестирование и calibration
Жестовые анимации обязательно тестируем на физическом устройстве — эмулятор не воспроизводит точную сенсорную физику. Slow Animations в симуляторе помогает поймать артефакты, но реальное ощущение — только на железе. Для калибровки параметров используем Xcode Instruments → Animation Hitches, чтобы убедиться, что GPU frame time не превышает 16ms на 60Hz-устройствах.
Срок реализации: 2–3 дня на один паттерн жестовой анимации, включая интеграцию и калибровку под конкретный дизайн.







