Najważniejsze
Timeline — to model czasu animacji, który określa, kiedy animacja się zaczyna, jak długo trwa i jak upływ czasu jest przekształcany w zmianę animowanego parametru. W przeciwieństwie do animacji klatkowej (frame-by-frame), gdzie każda klatka jest ustawiana ręcznie, animacja timeline jest opisywana parametrami: duration — całkowity czas trwania, startDelay — opóźnienie przed startem, repeatCount — liczba powtórzeń, i timingFunction — krzywa prędkości.
Oś czasu działa w znormalizowanej przestrzeni [0, 1] — local time lub progress. Wartość zerowa odpowiada początkowi animacji, jedynka — zakończeniu. Funkcja easing przekształca liniowy local time w rzeczywisty postęp animacji. Jeśli repeatCount > 1, timeline cyklicznie resetuje się (RESTART) lub odwraca (REVERSE). W iOS CAMediaTiming dodaje parametry speed (prędkość odtwarzania, >1 przyspiesza) i timeOffset (przesunięcie osi czasu). W Androidzie Animator używa setCurrentPlayTime() do bezpośredniego zarządzania pozycją na osi czasu. Według Material Design, animacje krótsze niż 100 ms nie są odbierane jako animacja, a dłuższe niż 500 ms — zaczynają irytować. Optymalny czas trwania dla animacji interfejsu to 200–350 ms.
W iOS zarządzanie osią czasu animacji odbywa się przez protokół CAMediaTiming, który implementują CAAnimation i CALayer. Udostępnia on właściwości: duration (czas trwania w sekundach), beginTime (czas startu względem nadrzędnego timeline), repeatCount (liczba powtórzeń), repeatDuration (całkowity czas trwania powtórzeń), autoreverses (automatyczny ruch wsteczny), fillMode (zachowanie przed/po animacji), speed (prędkość odtwarzania) i timeOffset (przesunięcie na osi).
CACurrentMediaTime() — funkcja zwracająca bezwzględny czas w sekundach od ostatniego restartu urządzenia. Używana jako podstawowy znacznik czasu do synchronizacji wielu animacji: ustaw beginTime = CACurrentMediaTime() + offset, aby uruchomić grupę animacji z dokładnym opóźnieniem. CAMediaTiming obsługuje również hierarchię timeline — nadrzędna warstwa może przyspieszać/spowalniać wszystkie podrzędne animacje przez właściwość speed. W UIKit UIViewPropertyAnimator (iOS 10+) abstrahuje oś czasu przez właściwości duration, delay i callbacki startAnimation/pauseAnimation/stopAnimation. W SwiftUI struktura Animation udostępnia duration jako część specyfikacji animacji, ale bezpośrednie zarządzanie osią czasu jest ukryte — do niestandardowego timeline użyj TimelineView (iOS 15+) z własnym schedulerem.
W Androidzie zarządzanie osią czasu jest scentralizowane w klasie Animator i jej dziedzicach (ValueAnimator, ObjectAnimator). Podstawowe właściwości timeline: duration (czas trwania w milisekundach), startDelay (opóźnienie przed startem), repeatCount (liczba powtórzeń), repeatMode (RESTART lub REVERSE). Metoda setCurrentPlayTime(long) pozwala poruszać się po osi czasu w dowolnej pozycji.
ValueAnimator dodaje poziom abstrakcji: generuje wartości od 0 do 1 (normalized time) na podstawie duration i przekazuje je do TimeInterpolator. AnimatorSet pozwala łączyć wiele Animator w jedną oś czasu z sekwencyjnym (playSequentially) lub równoległym (playTogether) wykonaniem. W Jetpack Compose oś czasu jest zarządzana przez AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) i keyframes (punkty na osi czasu z niestandardowym easing dla każdego odcinka). Compose AnimatedContent używa timeline dla transitionSpec — programista opisuje, jak treść pojawia się/znika z przesunięciami czasowymi. Według Android Performance, oś czasu animacji powinna być wielokrotnością 16 ms (cykl vsync) dla 60fps — nieprawidłowy czas trwania może powodować pomijanie klatek z powodu niezgodności z częstotliwością odświeżania ekranu.
Oś czasu staje się szczególnie ważna przy kompozycji wielu animacji. Trzy podstawowe wzorce: kaskada — animacje uruchamiane sekwencyjnie z przesunięciem (każda następna zaczyna się po offset od poprzedniej), równoległość — wszystkie animacje uruchamiane jednocześnie z tym samym timeline, i stagger — animacje uruchamiane z nakładaniem (druga zaczyna się przed zakończeniem pierwszej).
W iOS kaskada jest implementowana przez beginTime każdej CAAnimation = poprzednia beginTime + poprzednia duration. Dla stagger używa się CAAnimationGroup z różnymi beginTime w zagnieżdżonych animacjach. W Androidzie AnimatorSet obsługuje playSequentially i playTogether; stagger tworzy się przez ustawienie startDelay dla każdego Animator. W Compose AnimatedVisibility z enter/exitTransition używa stagger w wariancie staggerChildren do kaskadowego pojawiania się elementów listy. Na WWDC i Google I/O 2024 obie platformy przedstawiły narzędzia wizualizacji timeline: Xcode Animations Inspector i Android Studio Animations Timeline — pokazują one rzeczywistą oś czasu i pominięte klatki w czasie rzeczywistym.
Przykład animacji z niestandardowym timeline: opóźnienie 0.3 sek, czas trwania 0.6 sek, autoreverse i nieskończone powtórzenie. CAMediaTiming zarządza wszystkimi parametrami czasowymi.
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 daje opóźnienie 300 ms. autoreverses = true powoduje, że animacja idzie w przeciwnym kierunku po każdym cyklu. repeatCount = .infinity tworzy nieskończoną pętlę efektu „oddychania”. speed = 1.0 — normalna prędkość; przyspiesz animację 2 razy wartością 2.0.
SwiftUI TimelineView (iOS 15+) zapewnia dostęp do systemowego timeline dla niestandardowej animacji. Widok aktualizuje się przy każdym tiknięciu schedulera.
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 z .periodic scheduler generuje tiknięcia co 0.1 sek. W zamknięciu context.date — bieżący czas, który jest przekształcany w fazę sinusoidy. Rozmiar okręgu pulsuje z częstotliwością 1 Hz. TimelineView — jedyny sposób na stworzenie animacji z własną osią czasu w SwiftUI bez Animation API.
ValueAnimator z pełnym zarządzaniem osią czasu: opóźnienie, czas trwania, powtórzenie. Metoda setCurrentPlayTime pozwala skakać po osi czasu.
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 — opóźnienie przed startem. repeatMode = REVERSE zwraca animację do wartości początkowej. animatedFraction daje postęp od 0 do 1 z uwzględnieniem repeatMode. addUpdateListener wywołuje się co klatkę (zwykle 16 ms). Do wstrzymania użyj pause() / resume().
AnimatorSet pozwala tworzyć złożone osie czasu z wielu animacji. W przykładzie — sekwencyjne wykonanie z nakładaniem (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 uruchamia wszystkie animacje równolegle, ale każda ma swój startDelay — tworzy to efekt stagger: fadeIn startuje pierwszy (0 ms), slideUp po 100 ms, scale po 200 ms. Wynikowa oś czasu: 0 — 100 — 200 — 600 ms. Do sekwencyjnego wykonania użyj playSequentially.
Często zadawane pytania
Animator — abstrakcyjna klasa bazowa dla wszystkich animacji. ValueAnimator — jej podklasa animująca wartości liczbowe bez powiązania z obiektem. ObjectAnimator (dziedzic ValueAnimator) animuje konkretną właściwość obiektu (np. alpha lub translationY). Do timeline używaj ValueAnimator jako uniwersalnego timera z callbackiem przez addUpdateListener.
Używaj zegarów systemowych (CACurrentMediaTime w iOS, System.nanoTime w Android) do obliczania postępu, a nie liczby klatek. Do synchronizacji sieciowej stosuj timer NTP-based z korekcją offset. iOS obsługuje AVAudioSession do synchronizacji timeline audio-wideo z dokładnością do milisekund. Unikaj synchronizacji po klatkach — częstotliwość klatek waha się od 30 do 120 fps.
startOffset — opóźnienie przed rozpoczęciem animacji w milisekundach. Używane do kaskady i efektów stagger. repeatCount — liczba powtórzeń: 0 = brak powtórzeń, ValueAnimator.INFINITE = nieskończona pętla. repeatMode określa kierunek: RESTART (reset do początku) lub REVERSE (ruch wsteczny). W iOS startOffset nazywa się beginTime (w kombinacji z CACurrentMediaTime), repeatCount — to samo właściwość CABasicAnimation.
Material Design zaleca 200–350 ms dla animacji interfejsu. Animacje krótsze niż 100 ms nie są odbierane jako animacja, dłuższe niż 500 ms — irytują. Dla wejścia/wyjścia elementów: 200–300 ms. Dla przejść między ekranami: 300–400 ms. Używaj krótszych czasów dla małych elementów (100–200 ms) i dłuższych dla dużych (400–500 ms).
Szarpnięcia (jank) powstają z powodu: długotrwałych operacji w głównym wątku (blokują renderowanie), złożonej hierarchii View/Layer z overdraw, lub nieprawidłowego użycia timeline (czas trwania nie jest wielokrotnością vsync — 16ms dla 60fps, 8ms dla 120fps). Używaj Xcode Instruments Animations lub Android Studio Profiler do wykrywania pominiętych klatek. W Compose stosuj Modifier.drawWithCache do cache'owania złożonych rysunków.
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ż