Transition (animație de tranziție) — mecanismul de schimbare vizuală a unui conținut cu altul: ecrane, fragmente, view-uri sau stări ale interfeței. O tranziție implementată corect direcționează atenția utilizatorului, explică relația dintre elemente și face navigarea intuitivă. Conform Material Design Guidelines (2026), aplicațiile cu tranziții bine gândite arată o reținere a utilizatorilor cu 22% mai mare la a doua sesiune. Citiți mai multe despre alte tipuri de animație în ghidul general de animație.
Principalele puncte
Transition — este o animație care are loc la înlocuirea unei stări vizuale a interfeței cu alta. Spre deosebire de Property Animation (animația proprietății unui singur obiect), Transition gestionează un întreg set de modificări: apariția/disparția elementelor, schimbarea poziției, dimensiunii și proprietăților acestora într-o singură tranziție. Transition rezolvă problema schimbării line a conținutului astfel încât utilizatorul să înțeleagă ce s-a mutat unde.
Pe platformele mobile, Transition se aplică în trei scenarii principale: navigarea între ecrane (push, pop, modal present), schimbarea fragmentelor (Android FragmentTransaction) și schimbarea stării în cadrul ecranului (desfășurarea cardului, comutarea filelor). Fiecare scenariu necesită propria abordare: tranzițiile de navigare — UIViewControllerAnimatedTransitioning în iOS sau ActivityOptions în Android, tranzițiile intra-ecran — Transition Framework sau UIView.transition.
Conform Apple HIG (2026), durata unei tranziții standard nu trebuie să depășească 400–500 ms — animațiile mai lungi sunt percepute ca întârzieri. Material Design recomandă 300–350 ms pentru tranzițiile mobile. Pentru elemente mari (carduri, imagini), timpul poate crește până la 500–600 ms fără pierderea performanței percepute.
Tipurile de tranziții se împart în mai multe categorii în funcție de ce conținut se schimbă și cum. Tipuri de bază: estompare (cross-fade) — dispariția lină a unuia și apariția altuia; deplasare (slide) — un ecran pleacă, altul intră; scalare (scale/zoom) — un element se mărește, deschizând un ecran detaliat. Tranzițiile combinate îmbină mai multe efecte.
| Tip de tranziție | Descriere | iOS API | Android API |
|---|---|---|---|
| Cross-fade | Estompare: un conținut dispare, altul apare | UIView.transition(with:duration:options:.transitionCrossDissolve) | TransitionSet + Fade() |
| Slide | Deplasare: conținutul pleacă/sosește pe orizontală sau verticală | CATransition(type: .push, subtype: .fromLeft) | Slide() / FragmentTransaction.setCustomAnimations |
| Scale / Zoom | Scalare: elementul se mărește pe tot ecranul | UIViewPropertyAnimator cu animație scale | Scale() + ActivityOptions.makeScaleUpAnimation |
| Explode | Explozie: conținutul se sparge în bucăți la dispariție | CATransition(type: .reveal) | Explode() (API 21+) |
| Fade-through | Estompare prin ecran alb/negru | UIView.transition(with: .transitionCrossDissolve, cu fundal alb) | Fade() prin ChangeBounds |
Reguli Material Design: utilizați un singur tip de tranziție într-o aplicație pentru a crea un limbaj vizual unitar. Se recomandă slide pentru navigarea între ecrane de același nivel (master → detail), cross-fade pentru schimbarea conținutului în cadrul unui ecran (file, comutare vizualizare), scale/zoom pentru tranziția de la un card la un ecran detaliat (card produs → descriere).
Pe iOS UIView.transition este cea mai simplă modalitate de a anima schimbarea conținutului într-un singur View. Suportă cross-dissolve, curl-up, curl-down, flip-from-left, flip-from-right. Pentru tranziții de navigare între UIViewController se utilizează UINavigationControllerDelegate cu UIViewControllerAnimatedTransitioning — oferind control complet asupra animației push/pop.
import UIKit
// UIView.transition: schimbarea imaginii cu cross-dissolve
UIView.transition(with: imageView,
duration: 0.4,
options: [.transitionCrossDissolve, .curveEaseInOut]
) {
self.imageView.image = UIImage(named: "new_image")
} completion: { _ in
print("Transition finalizat")
}
// UIView.transition: schimbarea a două View-uri
UIView.transition(from: firstView, to: secondView,
duration: 0.5,
options: [.transitionFlipFromRight, .showHideTransitionViews]
) { _ in
// firstView ascuns, secondView afișat
}
// CATransition pentru strat (fără 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 — o alternativă modernă pentru tranziții personalizate pe iOS 10+. Spre deosebire de UIView.transition, PropertyAnimator suportă pauză, anulare și reverse (animație inversă), ceea ce este important pentru tranzițiile interactive (swipe-to-go-back). Pentru animația între UIViewController utilizați UIPercentDrivenInteractiveTransition împreună cu PropertyAnimator — acesta este modelul standard pentru navigarea interactivă.
Pe Android FragmentTransaction cu setCustomAnimations este principala modalitate de animare a tranzițiilor între Fragment. Parametri: enter (animația de intrare a noului Fragment), exit (animația de ieșire a vechiului), popEnter și popExit — pentru navigarea înapoi. FragmentTransaction suportă animații din resurse (R.anim) și Transition Framework (R.transition).
import androidx.fragment.app.FragmentTransaction
import androidx.transition.TransitionInflater
// FragmentTransaction cu animații din resurse
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 cu 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: animație la pornirea Activity
val options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0, view.width, view.height
).toBundle()
startActivity(intent, options)
Cele mai bune practici Android: Slide() — pentru înlocuirea Fragment în navigare (enter = Slide(Gravity.END), exit = Slide(Gravity.START)). Fade() — pentru afișarea/ascunderea overlay și dialogurilor. Explode() — pentru tranziții accentuate (ștergerea unui element din listă). Pentru dispozitive vechi (API < 21) utilizați resurse R.anim ca fallback. Transition Framework nu suportă SurfaceView, TextureView, GLSurfaceView — pentru video și cameră utilizați doar animații prin R.anim.
UIViewControllerAnimatedTransitioning — protocolul iOS pentru crearea de animații complet personalizate de tranziție între controlere. Oferă acces la View-ul container (transitionContext.containerView), View-ul sursă și cel țintă. În combinație cu UIPercentDrivenInteractiveTransition, această abordare permite crearea de tranziții interactive cu glisare cu degetul.
import UIKit
"> Animator de tranziție personalizat
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)
}
}
}
// Aplicare în UINavigationControllerDelegate
extension MainViewController: UINavigationControllerDelegate {
func navigationController(
_ nc: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from: UIViewController, to: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return ScaleTransitionAnimator(presenting: operation == .push)
}
}
Tranziții interactive: adăugați UIPanGestureRecognizer pe ecranul sursă. La declanșarea gestului, creați un UIPercentDrivenInteractiveTransition și apelați update(CGFloat(percent)). Pentru anulare la atingerea pragului (de obicei 30%) apelați cancel(). Acest model este utilizat implicit în UINavigationController la gestul pop și în UIScrollView cu pagingEnabled. Pentru gesturi personalizate, implementați UIViewControllerInteractiveTransitioning.
Transition Framework (android.transition) — este un sistem declarativ de animație care animează automat schimbările între două scene (Scene). O scenă este un set de View-uri cu o stare specifică. La tranziția de la Scene A la Scene B, Transition Framework analizează diferența și animează: schimbarea poziției (ChangeBounds), schimbarea dimensiunii (ChangeClipBounds, ChangeTransform), apariția (Fade) și dispariția (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: combinație ChangeBounds + Fade
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
// Tranziție între scene în 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 fără configurare manuală
TransitionManager.go(scene2, AutoTransition().apply {
duration = 350L
})
// TransitionManager.beginDelayedTransition — fără scene
TransitionManager.beginDelayedTransition(sceneRoot, ChangeBounds())
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
itemView.requestLayout() "> Modificarea layout-ului este animată automat
ChangeBounds — cea mai utilizată tranziție în Android, animează schimbarea poziției și dimensiunii View-ului. ChangeTransform animează rotation și scale. ChangeImageTransform — pentru animația ImageView (centerCrop → fitCenter). Pentru tranziții complexe cu schimbarea structurii ierarhiei, utilizați TransitionSet cu ordering = ORDERING_SEQUENTIAL (animațiile se execută secvențial, nu în paralel). Conform documentației Android Developers (2026), Transition Framework suportă toate View-urile standard, dar nu suportă animația suprafeței (SurfaceView, TextureView, VideoView).
Întrebări frecvente
Animation — animația proprietăților unui singur obiect (alpha, translation, scale). Transition — animația schimbării unui set de obiecte sau stări (fragment → fragment, scenă → scenă). Transition gestionează multiple modificări simultan: apariție, dispariție, deplasare, schimbare de dimensiune — și animează automat diferența dintre două stări. Animation necesită gestionarea manuală a fiecărei proprietăți.
Utilizați FragmentTransaction.setCustomAnimations cu Transition Framework: fragment1.exitTransition = Fade(Fade.OUT), fragment2.enterTransition = Slide(Gravity.END). Pentru secvență: setReorderingAllowed(true) — fragmentele vor fi reordonate pentru o tranziție paralelă lină. Pentru cazuri simple, slide_in_right / slide_out_left din R.anim sunt suficiente. Pentru navigarea înapoi, specificați popEnter și popExit.
Transition Framework animează doar proprietățile care nu cauzează recalcularea layout-ului. Pentru animația înălțimii, utilizați ChangeBounds — acesta animează automat schimbarea limitelor View-ului între două stări de layout. Apelați TransitionManager.beginDelayedTransition(viewGroup, ChangeBounds()) înainte de modificarea LayoutParams. Dacă înălțimea se schimbă prin WRAP_CONTENT, asigurați-vă că layout_height înainte și după este diferit — ChangeBounds nu va funcționa dacă ambele stări sunt match_parent.
Utilizați modificatorul .transition() cu AnyTransition: .transition(.move(edge: .trailing).combined(with: .opacity)). Pentru tranziții personalizate, implementați AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)). Pentru animația în NavigationStack, utilizați .navigationTransition(.slide) în iOS 18+. SwiftUI nu suportă UIViewControllerAnimatedTransitioning — doar tranziții declarative prin AnyTransition.
Utilizați Profile GPU Rendering (Android) sau Core Animation Instrument (iOS). Pentru Android, adăugați profilarea Transition prin adb shell setprop debug.transition.profiling 1 — în loguri vor apărea duratele fiecărei etape a tranziției. Pe iOS, activați Slow Animations în simulator. Metrici țintă: durată < 400 ms, FPS >= 55, frame drops — nu mai mult de 2 per tranziție. Pentru scene complexe cu 15+ View-uri animate simultan, utilizați ChangeBounds cu duration 300 ms.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și