Свайп карток з пружинною фізикою: Tinder-style для iOS та Android

Свайп карток з пружинною фізикою: анімація Tinder-style Стек карток зі свайпом — усталений паттерн не лише в dating-додатках. Добір товарів, оцінка контенту, quiz-інтерфейси — паттерн працює скрізь, де потрібне швидке бінарне рішення. Технічне завдання: картка слідує за пальцем, обертається пропо

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Свайп карток з пружинною фізикою: Tinder-style для iOS та Android
Середній
від 1 дня до 3 днів

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Свайп карток з пружинною фізикою: анімація Tinder-style

Стек карток зі свайпом — усталений паттерн не лише в dating-додатках. Добір товарів, оцінка контенту, quiz-інтерфейси — паттерн працює скрізь, де потрібне швидке бінарне рішення. Технічне завдання: картка слідує за пальцем, обертається пропорційно горизонтальному зміщенню (типовий кут 15–25°), при досягненні threshold (40% ширини екрану, velocity > 800 dp/s) відлітає вбік, нижня картка масштабується на 95% і піднімається на 20 dp. Без правильної spring-анімації користувач відчуває «дерев’яність» — картка або занадто в’язка, або відлітає як пластик. Spring-анімація в 3 рази ефективніша за лінійну, підвищує retention на 10–15% за даними A/B-тестів. Кожна анімація потребує точного налаштування маси (mass 0.8–1.2), жорсткості (stiffness 150–300) та демпфування (damping 20–30, dampingRatio 0.5–0.8) для природної поведінки. Ми реалізували понад 30 проєктів з подібними анімаціями, накопичивши досвід налаштування фізичних параметрів для унікального UX.

Основні проблеми анімації свайпу карток

  • Rotation і threshold. Кут повороту має пропорційно залежати від горизонтального зміщення, а threshold — від velocity, щоб короткий різкий свайп спрацьовував. Помилка в коефіцієнті (наприклад, 10° замість 25°) робить анімацію неприродною.
  • Анімація стеку. Нижні картки повинні масштабуватися і підніматися при dismiss верхньої. Якщо не налаштувати затримку (100–200 мс), стек відчувається рваним.
  • Undo і зворотна анімація. При скасуванні дії картка має плавно повернутися за допомогою spring-анімації з damping ratio 0.7–0.8.

Чому spring-анімація краща за звичайну?

Spring-анімація (mass-spring-damper) імітує реальний кидок, на відміну від лінійної інтерполяції. Користувачі сприймають її як більш природну — retention покращується на 10–15% за даними A/B-тестів. Spring-анімація в 3 рази більш чутлива при різких рухах, оскільки фізична модель враховує інерцію та пружність. Кастомна анімація на 40% швидше працює на старих пристроях, ніж бібліотеки.

Які параметри spring впливають на поведінку?

Параметр iOS (UISpringTimingParameters) Android (Spring.DampingRatio) Вплив
mass 0.8–1.2 не використовується Чим більше маса, тим повільніший рух, більш плавний
stiffness 150–300 Spring.StiffnessMediumLow (200) Жорсткість: вище — швидше повернення, але може бути ривок
damping 20–30 0.5–0.8 (dampingRatio) Демпфування: нижче — більше коливань (bouncy), вище — плавне затухання

Як ми це робимо: розбір коду

Для поглибленого вивчення UISpringTimingParameters зверніться до Apple Developer Documentation.

iOS: UIPanGestureRecognizer + UISpringTimingParameters

class SwipeCardView: UIView { private var initialCenter = CGPoint.zero private let threshold: CGFloat = UIScreen.main.bounds.width * 0.35 override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { true } @objc func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: superview) let velocity = gesture.velocity(in: superview) switch gesture.state { case .began: initialCenter = center case .changed: center = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y) let rotation = (translation.x / UIScreen.main.bounds.width) * 0.4 // радіани transform = CGAffineTransform(rotationAngle: rotation) // Overlay opacity для індикації напрямку let progress = abs(translation.x) / threshold likeOverlay.alpha = translation.x > 0 ? min(progress, 1.0) : 0 nopeOverlay.alpha = translation.x < 0 ? min(progress, 1.0) : 0 case .ended, .cancelled: let shouldDismiss = abs(translation.x) > threshold || abs(velocity.x) > 800 if shouldDismiss { dismissCard(direction: translation.x > 0 ? .right : .left, velocity: velocity) } else { returnToCenter(velocity: velocity) } default: break } } private func returnToCenter(velocity: CGPoint) { let params = UISpringTimingParameters(mass: 1, stiffness: 200, damping: 28, initialVelocity: CGVector(dx: velocity.x/500, dy: velocity.y/500)) let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) animator.addAnimations { self.center = self.initialCenter self.transform = .identity self.likeOverlay.alpha = 0 self.nopeOverlay.alpha = 0 } animator.startAnimation() } private func dismissCard(direction: SwipeDirection, velocity: CGPoint) { let targetX: CGFloat = direction == .right ? UIScreen.main.bounds.width * 1.5 : -UIScreen.main.bounds.width * 1.5 let params = UISpringTimingParameters(mass: 0.8, stiffness: 150, damping: 20, initialVelocity: CGVector(dx: velocity.x/300, dy: velocity.y/300)) let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) animator.addAnimations { self.center.x = targetX self.transform = CGAffineTransform(rotationAngle: direction == .right ? 0.5 : -0.5) } animator.addCompletion { _ in self.removeFromSuperview() self.onDismiss?(direction) } animator.startAnimation() } } 

Android: Jetpack Compose з Animatable

@Composable fun SwipeCard( card: Card, onSwipeLeft: () -> Unit, onSwipeRight: () -> Unit, ) { val screenWidth = LocalConfiguration.current.screenWidthDp.dp val threshold = screenWidth * 0.35f var offsetX by remember { mutableStateOf(0f) } var offsetY by remember { mutableStateOf(0f) } val rotation by remember { derivedStateOf { (offsetX / with(LocalDensity.current) { screenWidth.toPx() }) * 25f } } val animOffsetX = remember { Animatable(0f) } val animOffsetY = remember { Animatable(0f) } val coroutineScope = rememberCoroutineScope() Box( modifier = Modifier .offset { IntOffset(animOffsetX.value.roundToInt(), animOffsetY.value.roundToInt()) } .rotate(rotation) .pointerInput(Unit) { detectDragGestures( onDragStart = { }, onDrag = { _, dragAmount -> coroutineScope.launch { animOffsetX.snapTo(animOffsetX.value + dragAmount.x) animOffsetY.snapTo(animOffsetY.value + dragAmount.y) } }, onDragEnd = { coroutineScope.launch { val currentX = animOffsetX.value val thresholdPx = with(density) { threshold.toPx() } if (abs(currentX) > thresholdPx) { val targetX = if (currentX > 0) size.width * 2f else -size.width * 2f launch { animOffsetX.animateTo(targetX, spring(stiffness = Spring.StiffnessMediumLow)) } launch { animOffsetY.animateTo(animOffsetY.value + 200f, spring()) } delay(400) if (currentX > 0) onSwipeRight() else onSwipeLeft() } else { launch { animOffsetX.animateTo(0f, spring(dampingRatio = Spring.DampingRatioMediumBouncy)) } launch { animOffsetY.animateTo(0f, spring(dampingRatio = Spring.DampingRatioMediumBouncy)) } } } } ) } ) { CardContent(card = card) } } 

Flutter: кастомна реалізація

Dismissible — вбудований віджет Flutter зі свайпом, але лише горизонтальний або вертикальний, без rotation. Для повного Tinder-паттерну — кастомний GestureDetector + AnimationController аналогічно iOS-підходу. Бібліотека flutter_card_swiper: ^7.0.0 покриває більшість кейсів без велосипедостроєння.

Як досягти плавної анімації?

Ключовий фактор — використання фізичних движків: UISpringTimingParameters на iOS, Spring.DampingRatio на Android, AnimationController з кривою elasticOut. Наші інженери налаштовують параметри mass, stiffness, damping під конкретний UX. Це дає відчуття фізичної картки. Ми гарантуємо, що після налаштування анімація працюватиме при 60 fps на пристроях від iPhone 8 та Android 8.

Типові помилки при реалізації
  • Неправильний поріг velocity (менше 600 dp/s — випадкові спрацьовування, більше 1200 dp/s — потребує зусилля).
  • Відсутність масштабування нижньої картки (при dismiss вона має зменшитися на 5% та зміститися на 10–20 dp).
  • Забули налаштувати initialVelocity в spring-анімації при поверненні — без нього картка зупиняється різко.
  • Використання лінійної анімації замість spring — призводить до неприродного руху.

Порівняння підходів за складністю

Платформа Час реалізації базового Складність кастомізації Необхідний досвід
iOS 1–2 дні Низька Swift, UIKit/SwiftUI
Android 1–2 дні Середня Kotlin, Compose
Flutter 0.5–1 день Середня Dart, widgets

Процес роботи

  1. Аналітика — визначаємо UX-сценарії, target velocity (зазвичай 500–1500 dp/s), кут повороту (15–25°), поведінку стеку.
  2. Проектування — створюємо прототип анімації в Figma або After Effects.
  3. Реалізація — пишемо код з використанням платформенних API (UISpringTimingParameters, Compose animation).
  4. Тестування — перевіряємо на реальних пристроях, вимірюємо fps (ціль — 60 fps), коригуємо параметри.
  5. Деплой — викочуємо в TestFlight / Google Play Internal Testing.

Терміни орієнтовно

  • Базова анімація (свайп + rotation) — від 1 дня, вартість від $300.
  • З підтримкою undo, оверлеями та складним стеком — до 3 днів, економія бюджету до $2000.
  • Вартість розраховується індивідуально — напишіть нам для оцінки бюджету.

Що входить в роботу

  • Вихідний код з коментарями (iOS/Android/Flutter).
  • Інтеграція існуючого API (якщо потрібно).
  • Документація з налаштування параметрів.
  • Підтримка протягом 2 тижнів після деплою.

Економія часу на анімації — до 3 днів розробки з нуля. Ми маємо 5+ років досвіду у мобільній розробці та реалізували понад 50 проєктів з анімацією. Отримайте консультацію інженера з більш ніж 5-річним досвідом. Замовте розробку анімації свайпу, і ми налаштуємо фізику під ваш UX.