Key Takeaways
Timeline is an animation time model that defines when an animation starts, how long it lasts, and how time progression is transformed into changes of the animated parameter. Unlike frame-by-frame animation where each frame is set manually, timeline animation is described by parameters: duration — total length, startDelay — delay before start, repeatCount — number of repetitions, and timingFunction — the speed curve.
The timeline operates in normalized space [0, 1] — local time or progress. A zero value corresponds to the start of the animation, one to completion. An easing function converts linear local time into actual animation progress. If repeatCount > 1, the timeline cycles (RESTART) or inverts (REVERSE). In iOS, CAMediaTiming adds speed (playback rate, >1 accelerates) and timeOffset (timeline shift). In Android, Animator uses setCurrentPlayTime() for direct timeline position control. According to Material Design, animations shorter than 100 ms are not perceived as animations, while those longer than 500 ms start to irritate. The optimal duration for interface animations is 200–350 ms.
In iOS, animation timeline management is handled through the CAMediaTiming protocol, implemented by CAAnimation and CALayer. It provides properties: duration (in seconds), beginTime (start time relative to the parent timeline), repeatCount (number of repetitions), repeatDuration (total repeat duration), autoreverses (auto-reverse), fillMode (behavior before/after animation), speed (playback rate), and timeOffset (timeline shift).
CACurrentMediaTime() is a function that returns the absolute time in seconds since the last device reboot. It is used as a base timestamp for synchronizing multiple animations: set beginTime = CACurrentMediaTime() + offset to start a group of animations with precise delay. CAMediaTiming also supports timeline hierarchy — a parent layer can speed up/slow down all child animations through the speed property. In UIKit, UIViewPropertyAnimator (iOS 10+) abstracts the timeline through duration, delay properties and startAnimation/pauseAnimation/stopAnimation callbacks. In SwiftUI, the Animation structure provides duration as part of the animation specification, but direct timeline control is hidden — for custom timeline use TimelineView (iOS 15+) with a custom scheduler.
In Android, timeline management is centralized in the Animator class and its subclasses (ValueAnimator, ObjectAnimator). The main timeline properties are: duration (in milliseconds), startDelay (delay before start), repeatCount (number of repetitions), repeatMode (RESTART or REVERSE). The setCurrentPlayTime(long) method allows navigating the timeline to any arbitrary position.
ValueAnimator adds an abstraction layer: it generates values from 0 to 1 (normalized time) based on duration and passes them to TimeInterpolator. AnimatorSet allows combining multiple Animators into a single timeline with sequential (playSequentially) or parallel (playTogether) execution. In Jetpack Compose, the timeline is managed through AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness), and keyframes (points on the timeline with custom easing for each segment). Compose AnimatedContent uses a timeline for transitionSpec — the developer describes how content appears/disappears with time offsets. According to Android Performance, the animation timeline should be a multiple of 16 ms (vsync cycle) for 60fps — incorrect duration can cause frame drops due to mismatch with the display refresh rate.
The timeline becomes especially important when composing multiple animations. Three basic patterns: cascade — animations start sequentially with an offset (each next one starts after an offset from the previous), parallel — all animations start simultaneously with the same timeline, and stagger — animations start with overlap (the second starts before the first completes).
In iOS, cascade is implemented through each CAAnimation's beginTime = previous beginTime + previous duration. For stagger, CAAnimationGroup is used with different beginTime values for nested animations. In Android, AnimatorSet supports playSequentially and playTogether; stagger is created by setting startDelay for each Animator. In Compose, AnimatedVisibility with enter/exitTransition uses stagger in the staggerChildren variant for cascading appearance of list items. At WWDC and Google I/O 2024, both platforms introduced timeline visualization tools: Xcode Animations Inspector and Android Studio Animations Timeline — they show the actual timeline and dropped frames in real time.
An example of animation with a custom timeline: 0.3 sec delay, 0.6 sec duration, autoreverse, and infinite repeat. CAMediaTiming controls all timing parameters.
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 provides a 300 ms delay. autoreverses = true makes the animation run in reverse after each cycle. repeatCount = .infinity creates an infinite loop of the “breathing” effect. speed = 1.0 — normal speed; accelerate the animation 2x with a value of 2.0.
SwiftUI TimelineView (iOS 15+) provides access to the system timeline for custom animation. The View updates on each scheduler tick.
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 with .periodic scheduler generates ticks every 0.1 sec. In the closure, context.date is the current time, which is converted into a sine wave phase. The circle size pulses at 1 Hz frequency. TimelineView is the only way to create an animation with its own timeline in SwiftUI without the Animation API.
ValueAnimator with full timeline control: delay, duration, repetition. The setCurrentPlayTime method allows jumping along the timeline.
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 — delay before start. repeatMode = REVERSE returns the animation to the initial value. animatedFraction gives progress from 0 to 1 accounting for repeatMode. addUpdateListener fires every frame (typically 16 ms). Use pause() / resume() for pausing.
AnimatorSet allows creating complex timelines from multiple animations. The example demonstrates sequential execution with overlap (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 starts all animations in parallel, but each has its own startDelay — this creates a stagger effect: fadeIn starts first (0 ms), slideUp after 100 ms, scale after 200 ms. The resulting timeline: 0 — 100 — 200 — 600 ms. For sequential execution, use playSequentially.
Frequently Asked Questions
Animator is an abstract base class for all animations. ValueAnimator is its subclass that animates numeric values without binding to an object. ObjectAnimator (a subclass of ValueAnimator) animates a specific object property (e.g., alpha or translationY). For timeline, use ValueAnimator as a universal timer with a callback via addUpdateListener.
Use system clocks (CACurrentMediaTime in iOS, System.nanoTime in Android) to calculate progress, not frame count. For network synchronization, use an NTP-based timer with offset correction. iOS supports AVAudioSession for audio-video timeline synchronization with millisecond precision. Avoid frame-based synchronization — frame rates vary from 30 to 120 fps.
startOffset is the delay before animation start in milliseconds. Used for cascade and stagger effects. repeatCount is the number of repetitions: 0 = no repeat, ValueAnimator.INFINITE = infinite loop. repeatMode determines direction: RESTART (reset to beginning) or REVERSE (reverse direction). In iOS, startOffset is called beginTime (in combination with CACurrentMediaTime), repeatCount is the same property of CABasicAnimation.
Material Design recommends 200–350 ms for interface animations. Animations shorter than 100 ms are not perceived as animations, while those longer than 500 ms are irritating. For entry/exit of elements: 200–300 ms. For transitions between screens: 300–400 ms. Use shorter durations for small elements (100–200 ms) and longer ones for large elements (400–500 ms).
Jank occurs due to: long-running operations on the main thread (blocking rendering), complex View/Layer hierarchy with overdraw, or incorrect timeline usage (duration not a multiple of vsync — 16ms for 60fps, 8ms for 120fps). Use Xcode Instruments Animations or Android Studio Profiler to detect dropped frames. In Compose, use Modifier.drawWithCache to cache complex drawings.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also