Реалізація кастомних анімацій Page Transition у мобільних додатках
Стандартний slide-from-right на iOS та fade на Android вже не сприймаються як особливе — це мінімальний рівень. Проблеми починаються, коли навігація повинна передавати сенс: карточка розгортається у повноекранний детейл, фото з сітки «перелітає» в галерею, екран налаштувань виїжджає знизу з затемненням батьківського контенту. Кожен із цих переходів потребує окремого підходу.
Shared Element Transitions — найскладніше і найцінніше
Перехід, коли елемент з одного екрана «перелітає» на інший — не перерисовується, а саме перелітає — це Shared Element Transition (iOS називає це Hero Animation).
На iOS реалізуємо через кастомний UIViewControllerAnimatedTransitioning. Ключевий момент: елемент-джерело нужно «знімити» в containerView через snapshotView(afterScreenUpdates: false), сховати оригінал, анімувати снапшот по потрібній траєкторії та в кінці сховати снапшот, показавши destination-елемент. Якщо пропустити afterScreenUpdates: false — отримаємо білий прямокутник замість снапшота на перших кадрах.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let snapshot = sourceView.snapshotView(afterScreenUpdates: false) else { return }
snapshot.frame = sourceFrame
containerView.addSubview(destinationVC.view)
containerView.addSubview(snapshot)
sourceView.isHidden = true
UIView.animate(withDuration: duration, delay: 0,
usingSpringWithDamping: 0.85, initialSpringVelocity: 0.3) {
snapshot.frame = destinationFrame
} completion: { _ in
snapshot.removeFromSuperview()
self.sourceView.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
На Android — SharedElementCallback + ActivityOptions.makeSceneTransitionAnimation або Jetpack Compose SharedTransitionLayout з Modifier.sharedElement. Compose-підхід значно простіший та не потребує XML-розмітки transition-сцен.
У Flutter — Hero widget для простих випадків, flutter_animate або кастомний PageRouteBuilder для складних. Hero обмежений: не працює коректно з ListView всередину NestedScrollView й іноді клипає елемент на переході — тоді спасає flightShuttleBuilder.
Кастомні переходи без shared elements
Для екранів без спільних елементів реалізуємо кастомні PageRoute:
-
Expand з точки — екран «виростає» з натиснутої кнопки через
ClipOval→ClipRRect→ повний екран. Використовується для action-кнопок (FAB). -
Depth transition — поточний екран зменшується та відступає, новий наближається. Досягається через
transform: Matrix4.translationValues+scale. -
Stagger reveal — елементи нового екрана з'являються послідовно з затримкою через
AnimationStaggered. Добре працює для списків та dashboard-екранів.
Інтерактивні (перериваємі) переходи
Перехід, який можна перервати свайпом назад на середині — це UIPercentDrivenInteractiveTransition на iOS та BackHandler + AnimationController у Flutter. Важливо: анімація повинна «отпружинити» назад при незавершеному жесті, а не просто обрватися. Для цього completionCurve повинна бути .easeOut, а не .linear.
Процес роботи
Починаємо з Figma-прототипу або опису переходів. Визначаємо тип: shared element, expand, custom route. Реалізуємо під цільову платформу. Тестуємо перериваність жестом (якщо потрібно) та поведінку у режимі accessibility Reduce Motion — там анімації повинні бути замінені на простий fade.
Терміни: 1–3 дні на перехід, залежить від складності та числа екранів.







