Principalele
Timeline — este un model de timp al animației care definește când începe animația, cât durează și cum trecerea timpului este transformată în modificarea parametrului animat. Spre deosebire de animația cadru cu cadru (frame-by-frame), unde fiecare cadru este setat manual, animația timeline este descrisă prin parametri: duration — durata totală, startDelay — întârzierea înainte de start, repeatCount — numărul de repetări și timingFunction — curba vitezei.
Axa timpului funcționează în spațiul normalizat [0, 1] — local time sau progress. Valoarea zero corespunde începutului animației, iar unu — finalului. Funcția de easing transformă local time liniar în progresul real al animației. Dacă repeatCount > 1, timeline-ul se resetează ciclic (RESTART) sau se inversează (REVERSE). În iOS, CAMediaTiming adaugă parametrii speed (viteza de redare, >1 accelerează) și timeOffset (deplasarea axei timpului). În Android, Animator folosește setCurrentPlayTime() pentru gestionarea directă a poziției pe axa timpului. Potrivit Material Design, animațiile mai scurte de 100 ms nu sunt percepute ca animație, iar cele mai lungi de 500 ms — încep să irite. Durata optimă pentru animațiile de interfață este de 200–350 ms.
În iOS, gestionarea axei timpului animației se realizează prin protocolul CAMediaTiming, implementat de CAAnimation și CALayer. Acesta oferă proprietățile: duration (durata în secunde), beginTime (timpul de start față de timeline-ul părinte), repeatCount (numărul de repetări), repeatDuration (durata totală a repetărilor), autoreverses (mișcarea automată inversă), fillMode (comportamentul înainte/după animație), speed (viteza de redare) și timeOffset (deplasarea pe axă).
CACurrentMediaTime() — funcția care returnează timpul absolut în secunde de la ultima repornire a dispozitivului. Utilizată ca marcaj temporal de bază pentru sincronizarea mai multor animații: setați beginTime = CACurrentMediaTime() + offset pentru a porni un grup de animații cu o întârziere precisă. CAMediaTiming suportă, de asemenea, ierarhia timeline-ului — stratul părinte poate accelera/încetini toate animațiile copil prin proprietatea speed. În UIKit, UIViewPropertyAnimator (iOS 10+) abstractizează axa timpului prin proprietățile duration, delay și callback-urile startAnimation/pauseAnimation/stopAnimation. În SwiftUI, structura Animation oferă duration ca parte a specificației animației, dar gestionarea directă a axei timpului este ascunsă — pentru un timeline personalizat utilizați TimelineView (iOS 15+) cu propriul scheduler.
În Android, gestionarea axei timpului este centralizată în clasa Animator și în moștenitorii săi (ValueAnimator, ObjectAnimator). Proprietățile principale ale timeline-ului: duration (durata în milisecunde), startDelay (întârzierea înainte de start), repeatCount (numărul de repetări), repeatMode (RESTART sau REVERSE). Metoda setCurrentPlayTime(long) permite deplasarea pe axa timpului în orice poziție.
ValueAnimator adaugă un nivel de abstractizare: generează valori de la 0 la 1 (normalized time) pe baza duration și le transmite către TimeInterpolator. AnimatorSet permite combinarea mai multor Animator într-o singură axă a timpului cu execuție secvențială (playSequentially) sau paralelă (playTogether). În Jetpack Compose, axa timpului este gestionată prin AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) și keyframes (puncte pe axa timpului cu easing personalizat pentru fiecare segment). Compose AnimatedContent folosește timeline-ul pentru transitionSpec — dezvoltatorul descrie cum conținutul apare/dispare cu deplasări temporale. Potrivit Android Performance, axa timpului animației trebuie să fie un multiplu de 16 ms (ciclul vsync) pentru 60fps — o durată incorectă poate cauza omiterea cadrelor din cauza nepotrivirii cu rata de reîmprospătare a ecranului.
Axa timpului devine deosebit de importantă la compoziția mai multor animații. Trei modele de bază: cascadă — animațiile sunt pornite secvențial cu deplasare (fiecare următoare începe după offset de la precedenta), paralel — toate animațiile sunt pornite simultan cu același timeline și stagger — animațiile sunt pornite cu suprapunere (a doua începe înainte de finalizarea primei).
În iOS, cascada este implementată prin beginTime al fiecărei CAAnimation = beginTime anterioară + duration anterioară. Pentru stagger se folosește CAAnimationGroup cu diferite beginTime în animații imbricate. În Android, AnimatorSet suportă playSequentially și playTogether; stagger-ul se creează prin setarea startDelay pentru fiecare Animator. În Compose, AnimatedVisibility cu enter/exitTransition folosește stagger în varianta staggerChildren pentru apariția cascadată a elementelor listei. La WWDC și Google I/O 2024, ambele platforme au prezentat instrumente de vizualizare a timeline-ului: Xcode Animations Inspector și Android Studio Animations Timeline — acestea arată axa timpului reală și cadrele omise în timp real.
Exemplu de animație cu timeline personalizat: întârziere 0.3 sec, durată 0.6 sec, autoreverse și repetare infinită. CAMediaTiming gestionează toți parametrii temporali.
import UIKit
func breathingAnimation(layer: CALayer) {
let animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 0.6
animation.beginTime = CACurrentMediaTime() + 0.3
animation.fromValue = 1.0
animation.toValue = 0.3
animation.autoreverses = true
animation.repeatCount = .infinity
animation.speed = 1.0
animation.fillMode = .both
layer.add(animation, forKey: "breathing")
}
beginTime = CACurrentMediaTime() + 0.3 oferă o întârziere de 300 ms. autoreverses = true face ca animația să meargă în direcția opusă după fiecare ciclu. repeatCount = .infinity creează o buclă infinită a efectului de "respirație". speed = 1.0 — viteză normală; accelerați animația de 2 ori cu valoarea 2.0.
SwiftUI TimelineView (iOS 15+) oferă acces la timeline-ul sistemului pentru animație personalizată. Vederea se actualizează la fiecare tick al scheduler-ului.
import SwiftUI
struct TimelineViewExample: View {
var body: some View {
TimelineView(.periodic(from: .now, by: 0.1)) { context in
let seconds = context.date.timeIntervalSinceReferenceDate
let phase = sin(seconds * 2 * .pi)
Circle()
.fill(Color.blue)
.frame(width: 60 + 30 * phase)
.animation(.linear(duration: 0), value: phase)
}
}
}
TimelineView cu scheduler .periodic generează tick-uri la fiecare 0.1 sec. în închidere, context.date — timpul curent care este transformat în faza sinusoidei. Dimensiunea cercului pulsează cu frecvența de 1 Hz. TimelineView este singura modalitate de a crea o animație cu propria axă a timpului în SwiftUI fără Animation API.
ValueAnimator cu gestionarea completă a axei timpului: întârziere, durată, repetare. Metoda setCurrentPlayTime permite sărirea pe axa timpului.
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.View
fun View.timelineAnimation() {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 400
startDelay = 200
repeatCount = 2
repeatMode = ValueAnimator.REVERSE
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { animator ->
val progress = animator.animatedFraction
alpha = progress
translationX = progress * 100f
}
start()
}
}
startDelay = 200 ms — întârzierea înainte de start. repeatMode = REVERSE readuce animația la valoarea inițială. animatedFraction oferă progresul de la 0 la 1 ținând cont de repeatMode. addUpdateListener se declanșează la fiecare cadru (de obicei 16 ms). Pentru pauză utilizați pause() / resume().
AnimatorSet permite crearea unor axe ale timpului complexe din mai multe animații. În exemplu — execuție secvențială cu suprapunere (stagger).
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
fun View.staggerAnimation() {
val fadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 300
}
val slideUp = ObjectAnimator.ofFloat(this, "translationY", 100f, 0f).apply {
duration = 400
startDelay = 100
}
val scale = ObjectAnimator.ofFloat(this, "scaleX", 0.8f, 1f).apply {
duration = 250
startDelay = 200
}
AnimatorSet().apply {
playTogether(fadeIn, slideUp, scale)
start()
}
}
playTogether pornește toate animațiile în paralel, dar fiecare are propriul startDelay — aceasta creează efectul de stagger: fadeIn pornește primul (0 ms), slideUp după 100 ms, scale după 200 ms. Axa timpului rezultată: 0 — 100 — 200 — 600 ms. Pentru execuție secvențială utilizați playSequentially.
Întrebări frecvente
Animator — clasa de bază abstractă pentru toate animațiile. ValueAnimator — subclasa sa care animează valori numerice fără legătură cu un obiect. ObjectAnimator (moștenitorul ValueAnimator) animează o proprietate specifică a obiectului (de exemplu, alpha sau translationY). Pentru timeline, utilizați ValueAnimator ca timer universal cu callback prin addUpdateListener.
Utilizați ceasurile de sistem (CACurrentMediaTime în iOS, System.nanoTime în Android) pentru calcularea progresului, nu numărul de cadre. Pentru sincronizarea în rețea utilizați un timer bazat pe NTP cu corecție offset. iOS suportă AVAudioSession pentru sincronizarea timeline-ului audio-video cu precizie de milisecunde. Evitați sincronizarea pe cadre — rata de cadre variază de la 30 la 120 fps.
startOffset — întârzierea înainte de începerea animației în milisecunde. Folosit pentru cascadă și efecte stagger. repeatCount — numărul de repetări: 0 = fără repetare, ValueAnimator.INFINITE = buclă infinită. repeatMode determină direcția: RESTART (resetare la început) sau REVERSE (mișcare inversă). În iOS, startOffset se numește beginTime (în combinație cu CACurrentMediaTime), repeatCount — proprietatea omonimă a CABasicAnimation.
Material Design recomandă 200–350 ms pentru animațiile de interfață. Animațiile mai scurte de 100 ms nu sunt percepute ca animație, cele mai lungi de 500 ms — iritează. Pentru intrarea/ieșirea elementelor: 200–300 ms. Pentru tranziții între ecrane: 300–400 ms. Utilizați durate mai scurte pentru elemente mici (100–200 ms) și mai lungi pentru cele mari (400–500 ms).
Sacadarea (jank) apare din cauza: operațiilor lungi în firul principal (blochează randarea), a ierarhiei complexe View/Layer cu overdraw, sau a utilizării incorecte a timeline-ului (durata nu este un multiplu de vsync — 16ms pentru 60fps, 8ms pentru 120fps). Utilizați Xcode Instruments Animations sau Android Studio Profiler pentru detectarea cadrelor omise. în Compose, aplicați Modifier.drawWithCache pentru cache-ul desenelor complexe.
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