Transition (animacja przejścia) — mechanizm wizualnej zmiany jednej treści na inną: ekranów, fragmentów, widoków lub stanów interfejsu. Prawidłowo zaimplementowane przejście kieruje uwagę użytkownika, wyjaśnia wzajemne relacje między elementami i sprawia, że nawigacja staje się intuicyjna. Według Material Design Guidelines (2026), aplikacje z przemyślanymi przejściami wykazują o 22% wyższe utrzymanie użytkowników podczas drugiej sesji. Więcej o innych rodzajach animacji przeczytasz w ogólnym przewodniku po animacji.
Najważniejsze
Transition — to animacja, która zachodzi przy zastąpieniu jednego wizualnego stanu interfejsu innym. W przeciwieństwie do Property Animation (animacji właściwości jednego obiektu), Transition zarządza całym zestawem zmian: pojawianiem/znikaniem elementów, zmianą ich pozycji, rozmiaru i właściwości w ramach jednego przejścia. Transition rozwiązuje problem płynnej zmiany treści tak, aby użytkownik rozumiał, co gdzie się przemieściło.
Na platformach mobilnych Transition stosuje się w trzech głównych scenariuszach: nawigacja między ekranami (push, pop, modal present), zmiana fragmentów (Android FragmentTransaction) i zmiana stanu wewnątrz ekranu (rozwijanie karty, przełączanie zakładek). Każdy scenariusz wymaga własnego podejścia: przejścia nawigacyjne — UIViewControllerAnimatedTransitioning w iOS lub ActivityOptions w Android, przejścia wewnątrzekranowe — Transition Framework lub UIView.transition.
Według Apple HIG (2026), czas trwania standardowego przejścia nie powinien przekraczać 400–500 ms — dłuższe animacje są odbierane jako opóźnienie. Material Design zaleca 300–350 ms dla przejść mobilnych. Dla dużych elementów (karty, obrazy) czas może wzrosnąć do 500–600 ms bez utraty postrzeganej wydajności.
Rodzaje przejść dzielą się na kilka kategorii w zależności od tego, jaka treść się zmienia i jak. Podstawowe typy: zanikanie (cross-fade) — płynne znikanie jednego i pojawianie się drugiego; przesunięcie (slide) — jeden ekran odjeżdża, drugi wjeżdża; skalowanie (scale/zoom) — element powiększa się, otwierając szczegółowy ekran. Przejścia łączone łączą kilka efektów.
| Rodzaj przejścia | Opis | iOS API | Android API |
|---|---|---|---|
| Cross-fade | Zanikanie: jedna treść znika, inna się pojawia | UIView.transition(with:duration:options:.transitionCrossDissolve) | TransitionSet + Fade() |
| Slide | Przesunięcie: treść odjeżdża/wjeżdża poziomo lub pionowo | CATransition(type: .push, subtype: .fromLeft) | Slide() / FragmentTransaction.setCustomAnimations |
| Scale / Zoom | Skalowanie: element powiększa się na cały ekran | UIViewPropertyAnimator z animacją scale | Scale() + ActivityOptions.makeScaleUpAnimation |
| Explode | Rozpad: treść rozlatuje się na części przy znikaniu | CATransition(type: .reveal) | Explode() (API 21+) |
| Fade-through | Zanikanie przez biały/czarny ekran | UIView.transition(with: .transitionCrossDissolve, z białym tłem) | Fade() przez ChangeBounds |
Zasady Material Design: używaj jednego typu przejścia w obrębie jednej aplikacji, aby stworzyć spójny język wizualny. Zaleca się slide do nawigacji między ekranami tego samego poziomu (master → detail), cross-fade do zmiany treści wewnątrz jednego ekranu (zakładki, przełączanie widoku), scale/zoom do przejścia od karty do szczegółowego ekranu (karta produktu → opis).
Na iOS UIView.transition to najprostszy sposób animacji zmiany treści wewnątrz jednego View. Obsługuje cross-dissolve, curl-up, curl-down, flip-from-left, flip-from-right. Do przejść nawigacyjnych między UIViewController używa się UINavigationControllerDelegate z UIViewControllerAnimatedTransitioning — daje to pełną kontrolę nad animacją push/pop.
import UIKit
// UIView.transition: zmiana obrazu z cross-dissolve
UIView.transition(with: imageView,
duration: 0.4,
options: [.transitionCrossDissolve, .curveEaseInOut]
) {
self.imageView.image = UIImage(named: "new_image")
} completion: { _ in
print("Transition completed")
}
// UIView.transition: zmiana dwóch widoków
UIView.transition(from: firstView, to: secondView,
duration: 0.5,
options: [.transitionFlipFromRight, .showHideTransitionViews]
) { _ in
// firstView ukryty, secondView pokazany
}
// CATransition dla warstwy (bez Auto Layout)
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
transition.duration = 0.35
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
view.layer.add(transition, forKey: "pushTransition")
UIViewPropertyAnimator — nowoczesna alternatywa dla niestandardowych przejść na iOS 10+. W przeciwieństwie do UIView.transition, PropertyAnimator obsługuje pauzę, anulowanie i reverse (odwrotną animację), co jest ważne dla interaktywnych przejść (swipe-to-go-back). Do animacji między UIViewController użyj UIPercentDrivenInteractiveTransition w parze z PropertyAnimator — to standardowy wzorzec dla interaktywnej nawigacji.
Na Android FragmentTransaction z setCustomAnimations to podstawowy sposób animacji przejść między Fragment. Parametry: enter (animacja wejścia nowego Fragment), exit (animacja wyjścia starego), popEnter i popExit — do nawigacji wstecznej. FragmentTransaction obsługuje animacje zasobów (R.anim) i Transition Framework (R.transition).
import androidx.fragment.app.FragmentTransaction
import androidx.transition.TransitionInflater
// FragmentTransaction z animacjami zasobów
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_in_right, // enter
R.anim.slide_out_left, // exit
R.anim.slide_in_left, // popEnter
R.anim.slide_out_right // popExit
)
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
// FragmentTransaction z Transition Framework
val detailFragment = DetailFragment()
detailFragment.enterTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.slide_right)
detailFragment.exitTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.fade_out)
supportFragmentManager.beginTransaction()
.replace(R.id.container, detailFragment)
.addToBackStack(null)
.commit()
// ActivityOptions: animacja przy starcie Activity
val options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0, view.width, view.height
).toBundle()
startActivity(intent, options)
Najlepsze praktyki Android: Slide() — do zastępowania Fragment w nawigacji (enter = Slide(Gravity.END), exit = Slide(Gravity.START)). Fade() — do pokazywania/ukrywania overlay i dialogów. Explode() — do akcentowanych przejść (usuwanie elementu z listy). Dla starszych urządzeń (API < 21) używaj R.anim jako fallback. Transition Framework nie obsługuje SurfaceView, TextureView, GLSurfaceView — dla wideo i kamery używaj tylko animacji przez R.anim.
UIViewControllerAnimatedTransitioning — protokół iOS do tworzenia w pełni niestandardowych animacji przejścia między kontrolerami. Daje dostęp do View kontenera (transitionContext.containerView), źródłowego i docelowego View. W połączeniu z UIPercentDrivenInteractiveTransition to podejście pozwala tworzyć interaktywne przejścia z przewijaniem palcem.
import UIKit
"> Niestandardowy animator przejścia
class ScaleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
init(presenting: Bool) { self.isPresenting = presenting }
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toView = ctx.view(forKey: .to),
let fromView = ctx.view(forKey: .from)
else { return }
let container = ctx.containerView
let initialScale: CGAffineTransform = CGAffineTransform(scaleX: 0.8, y: 0.8)
let finalFrame = ctx.finalFrame(for: toView)
if isPresenting {
toView.transform = initialScale
toView.alpha = 0
toView.frame = finalFrame
container.addSubview(toView)
}
UIView.animate(withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.3) {
if self.isPresenting {
toView.transform = .identity
toView.alpha = 1
fromView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
fromView.alpha = 0
} else {
fromView.transform = initialScale
fromView.alpha = 0
}
} completion: { _ in
fromView.transform = .identity
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
// Zastosowanie w UINavigationControllerDelegate
extension MainViewController: UINavigationControllerDelegate {
func navigationController(
_ nc: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from: UIViewController, to: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return ScaleTransitionAnimator(presenting: operation == .push)
}
}
Przejścia interaktywne: dodaj UIPanGestureRecognizer do ekranu źródłowego. Po aktywacji gestu utwórz UIPercentDrivenInteractiveTransition i wywołaj update(CGFloat(percent)). Do anulowania po nieosiągnięciu progu (zwykle 30%) wywołaj cancel(). Ten wzorzec jest domyślnie używany w UINavigationController przy geście pop oraz w UIScrollView z pagingEnabled. Do niestandardowych gestów zaimplementuj UIViewControllerInteractiveTransitioning.
Transition Framework (android.transition) — to deklaratywny system animacji, który automatycznie animuje zmiany między dwiema scenami (Scene). Scena to zestaw View z określonym stanem. Podczas przejścia od Scene A do Scene B, Transition Framework analizuje różnicę i animuje: zmianę pozycji (ChangeBounds), zmianę rozmiaru (ChangeClipBounds, ChangeTransform), pojawianie się (Fade) i znikanie (Fade).
import android.transition.Scene
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.transition.AutoTransition
import android.transition.ChangeBounds
import android.transition.Fade
// TransitionSet: kombinacja ChangeBounds + Fade
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
// Przejście między scenami w ViewGroup
val sceneRoot = findViewById<ViewGroup>(R.id.scene_root)
val scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_initial)
val scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_expanded)
"> AutoTransition bez ręcznej konfiguracji
TransitionManager.go(scene2, AutoTransition().apply {
duration = 350L
})
// TransitionManager.beginDelayedTransition — bez scen
TransitionManager.beginDelayedTransition(sceneRoot, ChangeBounds())
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
itemView.requestLayout() "> Zmiana layout jest animowana automatycznie
ChangeBounds — najczęściej używane przejście w Android, animuje zmianę położenia i rozmiaru View. ChangeTransform animuje rotation i scale. ChangeImageTransform — do animacji ImageView (centerCrop → fitCenter). Do złożonych przejść ze zmianą struktury hierarchii używaj TransitionSet z ordering = ORDERING_SEQUENTIAL (animacje wykonywane są po kolei, a nie równolegle). Według dokumentacji Android Developers (2026), Transition Framework obsługuje wszystkie standardowe View, ale nie obsługuje animacji powierzchni (SurfaceView, TextureView, VideoView).
Często zadawane pytania
Animation — animacja właściwości jednego obiektu (alpha, translation, scale). Transition — animacja zmiany zestawu obiektów lub stanów (fragment → fragment, scena → scena). Transition zarządza wieloma zmianami jednocześnie: pojawianiem się, znikaniem, przemieszczaniem, zmianą rozmiaru — i automatycznie animuje różnicę między dwoma stanami. Animation wymaga ręcznego zarządzania każdą właściwością.
Użyj FragmentTransaction.setCustomAnimations z Transition Framework: fragment1.exitTransition = Fade(Fade.OUT), fragment2.enterTransition = Slide(Gravity.END). Do sekwencji: setReorderingAllowed(true) — fragmenty zostaną przeorganizowane dla płynnego równoległego przejścia. Do prostych przypadków wystarczy slide_in_right / slide_out_left z R.anim. Do nawigacji wstecznej określ popEnter i popExit.
Transition Framework animuje tylko właściwości, które nie powodują przeliczenia layoutu. Do animacji wysokości użyj ChangeBounds — automatycznie animuje zmianę granic View między dwoma stanami layoutu. Wywołaj TransitionManager.beginDelayedTransition(viewGroup, ChangeBounds()) przed zmianą LayoutParams. Jeśli wysokość zmienia się przez WRAP_CONTENT, upewnij się, że layout_height przed i po różni się — ChangeBounds nie zadziała, jeśli oba stany to match_parent.
Użyj modyfikatora .transition() z AnyTransition: .transition(.move(edge: .trailing).combined(with: .opacity)). Do niestandardowych przejść zaimplementuj AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)). Do animacji wewnątrz NavigationStack użyj .navigationTransition(.slide) w iOS 18+. SwiftUI nie obsługuje UIViewControllerAnimatedTransitioning — tylko deklaratywne przejścia przez AnyTransition.
Użyj Profile GPU Rendering (Android) lub Core Animation Instrument (iOS). Dla Android dodaj profilowanie Transition przez adb shell setprop debug.transition.profiling 1 — w logach pojawią się czasy trwania każdego etapu przejścia. Na iOS włącz Slow Animations w symulatorze. Docelowe metryki: czas trwania < 400 ms, FPS >= 55, frame drops — nie więcej niż 2 na przejście. Dla złożonych scen z 15+ jednocześnie animowanymi View używaj ChangeBounds z duration 300 ms.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również