Ми не раз бачили, як замовник приносить макет із діаграмою, де стовпці злітають один за одним, а лінія вимальовується наче рукою художника. На перший погляд — проста анімація. Але коли доходить до коду, з'ясовується, що готова бібліотека не вміє такого stagger-ефекту, а кастомна реалізація на Canvas потребує глибокого розуміння Core Animation або Android Canvas API. Ми беремося за такі задачі та за 1-3 дні впроваджуємо анімацію будь-якого рівня складності, будь то bar chart, line chart або кругова діаграма. У цій статті розберемо типові проблеми та способи їх вирішення.
Перед тим як обрати бібліотеку або писати з нуля, важливо розуміти, які анімації потрібні: просте з'явлення чи послідовний stagger, підтримка touch-подій та tooltip, робота з великими даними (500+ точок) без просідання FPS. Ми використовуємо профілювання (Instruments, Android Profiler) на кожному етапі, щоб гарантувати 60fps.
Як обрати між бібліотекою та кастомним Canvas?
Вибір між готовою бібліотекою та кастомною реалізацією — ключове рішення. Бібліотеки на зразок MPAndroidChart або Swift Charts дають 80% потрібного за 20% часу, але кастомізація анімації в них обмежена. Наприклад, MPAndroidChart підтримує animateX() і animateY(), але налаштувати easing per-bar або зробити stagger-анімацію (стовпці з'являються по черзі) — не можна без кастомного Renderer. Кастомний Canvas виправданий, коли потрібен нестандартний тип чарту, stagger-ефект, анімація окремих точок або інтерактивний tooltip.
| Критерій |
Бібліотека (MPAndroidChart, Swift Charts) |
Кастомний Canvas |
| Час реалізації |
Години |
1-3 дні |
| Кастомізація анімації |
Обмежена (easing, stagger) |
Повна (будь-яка крива, затримки) |
| Продуктивність 500+ точок |
Середня (можливі лаги) |
Висока (downsampling + LOD) |
| Підтримка жестів |
Вбудована (неглибока) |
Налаштовувана (будь-яка логіка) |
Чому stagger-анімація важлива для сприйняття даних?
Stagger-анімація (почергова поява елементів) привертає увагу до кожного стовпця або точки, роблячи дані більш читабельними. Кастомна реалізація на Canvas дає в 3 рази більше контролю над анімацією порівняно з готовими бібліотеками, що критично для складних дашбордів. Ми реалізували її на Flutter через AnimationController + Interval:
// Кожен стовпець анімується із затримкою 80мс
for (int i = 0; i < bars.length; i++) {
final start = (i * 0.08).clamp(0.0, 1.0);
final end = (start + 0.4).clamp(0.0, 1.0);
animations[i] = Tween(begin: 0.0, end: bars[i].value).animate(
CurvedAnimation(
parent: controller,
curve: Interval(start, end, curve: Curves.easeOutCubic),
),
);
}
На iOS аналогічний ефект досягається через CAKeyframeAnimation з keyTimes — кожен CAShapeLayer (один стовпець) отримує зміщений beginTime:
bars.enumerated().forEach { index, layer in
let anim = CABasicAnimation(keyPath: "bounds.size.height")
anim.beginTime = CACurrentMediaTime() + Double(index) * 0.08
anim.duration = 0.4
anim.timingFunction = CAMediaTimingFunction(name: .easeOut)
layer.add(anim, forKey: nil)
}
Line chart анімується через CAShapeLayer з strokeEnd: 0 → 1. Для додавання точок-маркерів, які з'являються в момент проходження «пензля», потрібно синхронізувати появу CALayer з прогресом strokeEnd. Реалізуємо через CAAnimationDelegate.animationDidStop для кожного сегмента або через displayLink з відстеженням presentation().strokeEnd.
Інтерактивний tooltip: як не втратити FPS
Tooltip, що слідує за пальцем по графіку — окрема задача. На iOS: UIGestureRecognizer → перерахунок координати в значення даних → UIView.animate для tooltip. Важно використовувати setNeedsDisplay() лише для тієї частини CALayer, де малюється highlight-лінія — перемальовка всього canvas вбиває FPS при швидкому русі пальця. Ми гарантуємо 60fps навіть при інтенсивній взаємодії завдяки інкрементальному рендерингу.
Що входить в роботу
- Вихідний код анімованого компонента на Swift/Kotlin/Dart/TypeScript
- Документація з кастомізації (кольори, шрифти, параметри анімації)
- Інтеграція в існуючий проєкт та налаштування тестового оточення
- Оптимізація продуктивності (downsampling для наборів >500 точок, LOD)
- Гарантія 60fps на пристроях середнього сегменту
Ми розробляємо мобільні застосунки більше 5 років та реалізували понад 15 проєктів з анімованою графікою (фінтех, аналітичні дашборди). Для роботи використовуємо сучасний стек: SwiftUI + Combine, Jetpack Compose + Coroutines, Flutter 3.x. Рекомендації Apple з анімації: використовуйте CAMediaTiming для синхронізації.
Як реалізувати stagger-анімацію за 4 кроки
- Ініціалізуйте
AnimationController (або CADisplayLink).
- Створіть масив затримок:
delay = index * intervalTime.
- Для кожного елемента застосуйте анімацію
Tween з CurvedAnimation та Interval.
- Запустіть загальний контролер — елементи почнуть анімуватися послідовно.
Що таке downsampling і навіщо він потрібен?
Downsampling (проріджування точок) застосовується при відмальовці великих наборів даних (500+ точок) для збереження 60fps. [Алгоритм Рамера–Дугласа–Пекера](https://ru.wikipedia.org/wiki/Алгоритм_Рамера_—_Дугласа_—_Пекера) видаляє точки, що лежать на прямій в межах заданого епсилон, зберігаючи візуальну форму графіка.
| Платформа |
Механізм анімації |
Ключові класи |
| iOS |
Core Animation (CALayer) |
CABasicAnimation, CAKeyframeAnimation |
| Android |
Canvas + ValueAnimator |
ValueAnimator, ObjectAnimator |
| Flutter |
AnimationController + Tween |
AnimationController, Tween, Interval |
| React Native |
Animated API |
Animated.Value, Animated.timing |
Зв'яжіться з нами, щоб обговорити ваш графік — оцінимо проєкт безкоштовно. Замовте розробку анімації графіка під ключ.
Анімації в мобільних додатках: Lottie, Rive, Spring та Reanimated
Ми зробили анімації для десятків проєктів — від ігрових інтерфейсів до bank-grade додатків. Знаємо, як змусити 120 fps працювати навіть на Android з ProGuard. Якщо анімація смикається — проблема не в інструменті, а у виборі підходу. Нижче покажемо, як обирати між Lottie та Rive, чому Spring physics перемагає UIView.animate, і як Reanimated 3 вичавлює 60 fps на старих пристроях. Отримайте консультацію щодо вашого проєкту — оцінимо анімаційний шар безкоштовно.
Чому UIView.animate ламається на складних сценаріях
UIView.animate(withDuration:) та ObjectAnimator на Android — правильний вибір для простих переходів. Але щойно анімація стає інтерактивною (користувач тягне елемент, швидкість залежить від жесту), потрібен інший підхід.
На iOS для gesture-driven анімації правильний інструмент — UIViewPropertyAnimator. Він дозволяє призупиняти, обертати та модифікувати анімацію в процесі. Типовий кейс: bottom sheet, який слідує за пальцем, продовжує рух з інерцією після відпускання та притягується до найближчої позиції. З UIView.animate це або не працює зовсім, або потребує ручної фізики.
У SwiftUI withAnimation працює з коробки, але інтерактивність обмежена — немає прямого аналога UIViewPropertyAnimator. Обхідний шлях: .gesture(DragGesture()) + @GestureState + явне обчислення позиції. Або йдемо в SwiftUI Animations API з Animation.spring(duration:bounce:).
Як React Native Reanimated обходить JS-міст
React Native Animated API виконує анімації в JS thread — це джерело джанка при завантаженому bridge. Reanimated 3 вирішує проблему через worklets: функції, які компілюються та виконуються прямо на UI thread без перетину JS-моста.
Приклад: parallax scroll header. На базовому Animated.Value при швидкому скролі FPS падає до 40-45 на mid-range Android. На Reanimated з useAnimatedScrollHandler — стабільні 60 fps, тому що весь перерахунок позиції відбувається на UI thread.
Reanimated 3 з useSharedValue, useAnimatedStyle та withSpring/withTiming — стандарт для анімацій у React Native. Gesture Handler v2 щільно інтегрований: useAnimatedGestureHandler замінює PanResponder і теж працює на UI thread.
Чому Lottie може знижувати FPS на Android і як це виправити
Lottie експортує After Effects анімацію в JSON. На iOS з lottie-ios працює стабільно, але на Android при складних ефектах (blur, частинки, градієнти) векторний рендеринг через Canvas призводить до просадок до 30-40 fps. Рішення — або спрощувати анімацію, або використовувати Rive з апаратним рендерингом. Ми тестували: Lottie-файл з розмиттям 5 МБ на Xiaomi Redmi Note 10 дає 48 fps, аналогічна анімація в Rive (.riv 400 КБ) — 60 fps. Rive працює в 1.25 раза швидше на однаковому пристрої, а при складних ефектах — до 4 разів легше за розміром.
Як вибрати між Lottie та Rive для вашого проєкту?
Обидва інструменти вирішують задачу «дизайнер робить анімацію, розробник додає файл». Але принципово по-різному.
| Критерій |
Lottie |
Rive |
| Формат |
JSON векторна анімація |
Бінарний .riv |
| Інтерактивність |
Ні (лінійне відтворення) |
State Machine, реакції на ввід |
| Продуктивність |
Середня (blur/частинки важкі) |
Апаратний рендеринг Metal/OpenGL |
| Розмір файлу |
2-5 МБ |
200-500 КБ |
| Підтримка платформ |
iOS, Android, Web, Flutter, RN |
iOS, Android, Web, Flutter, RN |
Вибір простий: статична декоративна анімація (splash screen, onboarding ілюстрації) — Lottie. Інтерактивні UI-елементи зі станами — Rive. Наприклад, кнопка з hover, pressed, loading, success — одна Rive-анімація з чотирма станами проти чотирьох окремих Lottie-файлів. Це зменшує обсяг коду та спрощує підтримку.
Як впровадити анімації Lottie за 4 кроки?
-
Експорт з After Effects через плагін Bodymovin — отримуєте
.json.
-
Додавання файлу до ресурсів проєкту (assets або drawable).
-
Ініціалізація плеєра — в iOS використовуйте
LOTAnimationView, в Android — LottieAnimationView.
-
Запуск з параметрами —
loopMode, animationSpeed, contentMode. Для складних сценаріїв — кастомний AnimationListener.
На практиці цей підхід працює, але потребує контролю версій Lottie-файлів. Ми рекомендуємо версіонувати їх разом з кодом і тестувати на цільових пристроях.
Spring-фізика та Hero transitions: як досягти природності
Spring-анімація відчувається природньо тому що імітує фізику — масу, жорсткість і демпфування. У SwiftUI: Animation.spring(response:dampingFraction:). В Android Compose: spring(dampingRatio = Spring.DampingRatioMediumBouncy).
Для Hero-переходів (елемент «перелітає» між екранами) на iOS використовуємо UIViewControllerTransitioningDelegate + UIViewControllerAnimatedTransitioning. У SwiftUI — matchedTransitionSource + navigationTransition(.zoom). На Flutter — Hero віджет, який працює з коробки.
Як уникнути типових помилок у Hero transitions?
Анімація починається нормально, але на цільовому екрані елемент «стрибає» в фінальну позицію. Причина — AutoLayout constraints застосовуються до завершення анімації. Рішення: layoutIfNeeded() в блоці анімації або використання transform замість frame-змін. На Flutter — перевіряйте HeroFlightShuttleConfiguration. Також слідкуйте за VSync: на Android при 90 Гц дисплеї Hero-перехід може виглядати смиканим, якщо не виставлений Choreographer.
Таблиця: Строки виконання та типові сценарії
| Задача |
Час |
Коментар |
| Базові екранні переходи та мікроінтеракції |
1 тиждень |
Стандартні gesture-driven sheets, каруселі |
| Lottie/Rive інтеграція з дизайн-системою |
3-5 днів |
Після отримання фінальних файлів |
| Кастомна інтерактивність з фізикою (сustom sheet, drawer) |
1-2 тижні |
Включає тестування на 4+ пристроях |
Що входить в роботу: анімаційний шар під ключ
- Інтеграція Lottie/Rive-файлів в дизайн-систему
- Код gesture-driven переходів (bottom sheets, drawers, каруселі)
- Тестування на реальних пристроях (iOS, Android)
- Документація з анімацій (архітектура, ключі станів)
- Підтримка при оновленні дизайну (гарантія 30 днів)
У нас 5+ років досвіду в мобільній розробці, понад 30 проєктів з анімаціями, сертифіковані iOS/Android розробники. Замовте анімації під ключ — перша консультація безкоштовна. Напишіть нам у месенджер, щоб узгодити деталі.
Apple Developer Documentation – UIKit Animations використовується як базова специфікація.