Hlavní body
Timeline — je časový model animace, který určuje, kdy animace začíná, jak dlouho trvá a jak je plynutí času převedeno na změnu animovaného parametru. Na rozdíl od snímkové animace (frame-by-frame), kde je každý snímek nastaven ručně, je timeline-animace popsána parametry: duration — celkové trvání, startDelay — zpoždění před startem, repeatCount — počet opakování a timingFunction — křivka rychlosti.
Časová osa pracuje v normalizovaném prostoru [0, 1] — local time nebo progress. Nulová hodnota odpovídá začátku animace, jednička — dokončení. Funkce easing převádí lineární local time na skutečný průběh animace. Pokud je repeatCount > 1, timeline se cyklicky resetuje (RESTART) nebo invertuje (REVERSE). V iOS přidává CAMediaTiming parametry speed (rychlost přehrávání, >1 zrychluje) a timeOffset (posun časové osy). V Androidu používá Animator setCurrentPlayTime() pro přímé řízení pozice na časové ose. Podle Material Design nejsou animace kratší než 100 ms vnímány jako animace a delší než 500 ms začínají dráždit. Optimální délka pro rozhraní animace je 200–350 ms.
V iOS se řízení časové osy animace provádí prostřednictvím protokolu CAMediaTiming, který implementují CAAnimation a CALayer. Poskytuje vlastnosti: duration (doba trvání v sekundách), beginTime (čas startu vzhledem k nadřazené timeline), repeatCount (počet opakování), repeatDuration (celková doba opakování), autoreverses (automatický zpětný chod), fillMode (chování před/po animaci), speed (rychlost přehrávání) a timeOffset (posun na časové ose).
CACurrentMediaTime() — funkce vracející absolutní čas v sekundách od posledního restartu zařízení. Používá se jako základní časové razítko pro synchronizaci více animací: nastavte beginTime = CACurrentMediaTime() + offset pro spuštění skupiny animací s přesným zpožděním. CAMediaTiming také podporuje hierarchii timeline — nadřazená vrstva může zrychlit/zpomalit všechny podřízené animace prostřednictvím vlastnosti speed. V UIKit UIViewPropertyAnimator (iOS 10+) abstrahuje časovou osu pomocí vlastností duration, delay a callbacků startAnimation/pauseAnimation/stopAnimation. V SwiftUI poskytuje struktura Animation duration jako součást specifikace animace, ale přímé řízení časové osy je skryté — pro vlastní timeline použijte TimelineView (iOS 15+) s vlastním schedulerem.
V Androidu je řízení časové osy centralizováno ve třídě Animator a jejích podtřídách (ValueAnimator, ObjectAnimator). Hlavní vlastnosti timeline: duration (doba trvání v milisekundách), startDelay (zpoždění před startem), repeatCount (počet opakování), repeatMode (RESTART nebo REVERSE). Metoda setCurrentPlayTime(long) umožňuje pohyb po časové ose na libovolnou pozici.
ValueAnimator přidává úroveň abstrakce: generuje hodnoty od 0 do 1 (normalized time) na základě duration a předává je TimeInterpolatoru. AnimatorSet umožňuje kombinovat více Animatorů do jediné časové osy s sekvenčním (playSequentially) nebo paralelním (playTogether) prováděním. V Jetpack Compose je časová osa řízena pomocí AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) a keyframes (body na časové ose s vlastním easingem pro každý segment). Compose AnimatedContent používá timeline pro transitionSpec — vývojář popisuje, jak se obsah objevuje/mizí s časovými posuny. Podle Android Performance by měla být časová osa animace násobkem 16 ms (vsync cyklus) pro 60fps — nesprávná délka může způsobit ztrátu snímků kvůli nesouladu s obnovovací frekvencí displeje.
Časová osa je obzvláště důležitá při kompozici více animací. Tři základní vzory: kaskáda — animace se spouštějí sekvenčně s posunem (každá další začíná po offsetu od předchozí), paralela — všechny animace se spouštějí současně se stejnou časovou osou a stagger — animace se spouštějí s překryvem (druhá začíná před dokončením první).
V iOS je kaskáda implementována pomocí beginTime každé CAAnimation = předchozí beginTime + předchozí duration. Pro stagger se používá CAAnimationGroup s různými beginTime ve vnořených animacích. V Androidu AnimatorSet podporuje playSequentially a playTogether; stagger se vytváří nastavením startDelay pro každý Animator. V Compose používá AnimatedVisibility s enter/exitTransition stagger ve variantě staggerChildren pro kaskádové zobrazování prvků seznamu. Na WWDC a Google I/O 2024 představily obě platformy nástroje pro vizualizaci časové osy: Xcode Animations Inspector a Android Studio Animations Timeline — zobrazují skutečnou časovou osu a ztracené snímky v reálném čase.
Příklad animace s vlastní časovou osou: zpoždění 0.3 s, trvání 0.6 s, autoreverse a nekonečné opakování. CAMediaTiming řídí všechny časové parametry.
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 poskytuje zpoždění 300 ms. autoreverses = true způsobí, že animace po každém cyklu běží opačným směrem. repeatCount = .infinity vytváří nekonečnou smyčku efektu „dýchání". speed = 1.0 — normální rychlost; zrychlete animaci 2krát hodnotou 2.0.
SwiftUI TimelineView (iOS 15+) poskytuje přístup k systémové časové ose pro vlastní animaci. Pohled se aktualizuje při každém tiknutí scheduleru.
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 s .periodic schedulerkem generuje tiknutí každých 0.1 s. V uzávěru context.date — aktuální čas, který je převeden na fázi sinusoidy. Velikost kruhu pulzuje s frekvencí 1 Hz. TimelineView je jediný způsob, jak vytvořit animaci s vlastní časovou osou ve SwiftUI bez Animation API.
ValueAnimator s plným řízením časové osy: zpoždění, trvání, opakování. Metoda setCurrentPlayTime umožňuje skákání po časové ose.
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 — zpoždění před startem. repeatMode = REVERSE vrací animaci na počáteční hodnotu. animatedFraction poskytuje průběh od 0 do 1 s ohledem na repeatMode. addUpdateListener se spouští každý snímek (obvykle 16 ms). Pro pozastavení použijte pause() / resume().
AnimatorSet umožňuje vytvářet složité časové osy z více animací. V příkladu — sekvenční provádění s překryvem (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 spouští všechny animace paralelně, ale každá má vlastní startDelay — to vytváří stagger efekt: fadeIn startuje první (0 ms), slideUp po 100 ms, scale po 200 ms. Výsledná časová osa: 0 — 100 — 200 — 600 ms. Pro sekvenční provádění použijte playSequentially.
Často kladené otázky
Animator — abstraktní základní třída pro všechny animace. ValueAnimator je jeho podtřída animující číselné hodnoty bez vazby na objekt. ObjectAnimator (dědic ValueAnimator) animuje konkrétní vlastnost objektu (např. alpha nebo translationY). Pro timeline použijte ValueAnimator jako univerzální časovač s callbackem přes addUpdateListener.
Použijte systémové hodiny (CACurrentMediaTime v iOS, System.nanoTime v Androidu) pro výpočet průběhu, ne počet snímků. Pro síťovou synchronizaci použijte NTP-based časovač s korekcí offsetu. iOS podporuje AVAudioSession pro synchronizaci audio-video časové osy s milisekundovou přesností. Vyhněte se synchronizaci podle snímků — snímková frekvence se pohybuje od 30 do 120 fps.
startOffset — zpoždění před začátkem animace v milisekundách. Používá se pro kaskádové a stagger efekty. repeatCount — počet opakování: 0 = bez opakování, ValueAnimator.INFINITE = nekonečná smyčka. repeatMode určuje směr: RESTART (reset na začátek) nebo REVERSE (zpětný chod). V iOS se startOffset nazývá beginTime (v kombinaci s CACurrentMediaTime), repeatCount — stejnojmenná vlastnost CABasicAnimation.
Material Design doporučuje 200–350 ms pro animace rozhraní. Animace kratší než 100 ms nejsou vnímány jako animace, delší než 500 ms — dráždí. Pro vstup/výstup prvků: 200–300 ms. Pro přechody mezi obrazovkami: 300–400 ms. Používejte kratší délky pro malé prvky (100–200 ms) a delší pro velké (400–500 ms).
Škubání (jank) vzniká kvůli: dlouhým operacím v hlavním vlákně (blokují vykreslování), složité hierarchii View/Layer s overdraw nebo nesprávnému použití timeline (délka není násobkem vsync — 16ms pro 60fps, 8ms pro 120fps). Použijte Xcode Instruments Animations nebo Android Studio Profiler pro detekci ztracených snímků. V Compose použijte Modifier.drawWithCache pro cachování složitých kreseb.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také