Головне
Timeline — це модель часу анімації, яка визначає, коли анімація починається, як довго триває і як хід часу перетворюється на зміну анімованого параметра. На відміну від покадрової анімації (frame-by-frame), де кожен кадр задається вручну, анімація з часовою шкалою описується параметрами: duration — загальна тривалість, startDelay — затримка перед стартом, repeatCount — кількість повторень, та timingFunction — крива швидкості.
Часова шкала працює в нормалізованому просторі [0, 1] — локальний час або прогрес. Нульове значення відповідає початку анімації, одиниця — завершенню. Функція easing (згладжування) перетворює лінійний локальний час у фактичний прогрес анімації. Якщо repeatCount > 1, часова шкала циклічно скидається (RESTART) або інвертується (REVERSE). В iOS CAMediaTiming додає параметри speed (швидкість відтворення, >1 прискорює) та timeOffset (зсув часової шкали). В Android Animator використовує setCurrentPlayTime() для прямого керування позицією на часовій шкалі. За даними Material Design, анімації коротші за 100 мс не сприймаються як анімація, а довші за 500 мс — починають дратувати. Оптимальна тривалість для інтерфейсних анімацій — 200–350 мс.
В iOS керування часовою шкалою анімації здійснюється через протокол CAMediaTiming, який реалізують CAAnimation та CALayer. Він надає властивості: duration (тривалість у секундах), beginTime (час старту відносно батьківської часової шкали), repeatCount (кількість повторень), repeatDuration (загальна тривалість повторень), autoreverses (автообернений хід), fillMode (поведінка до/після анімації), speed (швидкість відтворення) та timeOffset (зсув по шкалі).
CACurrentMediaTime() — функція, що повертає абсолютний час у секундах від останнього перезавантаження пристрою. Використовується як базова часова мітка для синхронізації кількох анімацій: встановіть beginTime = CACurrentMediaTime() + offset для запуску групи анімацій з точною затримкою. CAMediaTiming також підтримує ієрархію часових шкал — батьківський шар може прискорювати/уповільнювати всі дочірні анімації через властивість speed. В UIKit UIViewPropertyAnimator (iOS 10+) абстрагує часову шкалу через властивості duration, delay та колбеки startAnimation/pauseAnimation/stopAnimation. В SwiftUI структура Animation надає duration як частину специфікації анімації, але пряме керування часовою шкалою приховане — для спеціальної часової шкали використовуйте TimelineView (iOS 15+) з власним scheduler.
В Android керування часовою шкалою централізовано в класі Animator та його нащадках (ValueAnimator, ObjectAnimator). Основні властивості часової шкали: duration (тривалість у мілісекундах), startDelay (затримка перед стартом), repeatCount (кількість повторень), repeatMode (RESTART або REVERSE). Метод setCurrentPlayTime(long) дозволяє переміщатися по часовій шкалі в довільну позицію.
ValueAnimator додає рівень абстракції: він генерує значення від 0 до 1 (нормалізований час) на основі duration та передає їх у TimeInterpolator. AnimatorSet дозволяє комбінувати кілька Animator в єдину часову шкалу з послідовним (playSequentially) або паралельним (playTogether) виконанням. В Jetpack Compose часова шкала керується через AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) та keyframes (точки на часовій шкалі з власним easing для кожного відрізка). Compose AnimatedContent використовує часову шкалу для transitionSpec — розробник описує, як контент з'являється/зникає з часовими зсувами. За даними Android Performance, часова шкала анімації має бути кратною 16 мс (vsync-цикл) для 60fps — неправильна тривалість може викликати пропуск кадрів через невідповідність з частотою оновлення дисплея.
Часова шкала стає особливо важливою при композиції кількох анімацій. Три базові патерни: каскад — анімації запускаються послідовно зі зсувом (кожна наступна починається через offset після попередньої), паралель — всі анімації запускаються одночасно з однаковою часовою шкалою, та stagger — анімації запускаються з перекриттям (друга починається до завершення першої).
В iOS каскад реалізується через beginTime кожної CAAnimation = попередня beginTime + попередня duration. Для stagger використовується CAAnimationGroup з різними beginTime у вкладених анімаціях. В Android AnimatorSet підтримує playSequentially та playTogether; stagger створюється встановленням startDelay для кожного Animator. В Compose AnimatedVisibility з enter/exitTransition використовує stagger у варіанті staggerChildren для каскадної появи елементів списку. На WWDC та Google I/O 2024 обидві платформи представили інструменти візуалізації часової шкали: Xcode Animations Inspector та Android Studio Animations Timeline — вони показують фактичну часову шкалу та пропущені кадри в реальному часі.
Приклад анімації зі спеціальною часовою шкалою: затримка 0,3 с, тривалість 0,6 с, autoreverse та нескінченне повторення. CAMediaTiming керує всіма часовими параметрами.
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 дає затримку 300 мс. autoreverses = true змушує анімацію йти в зворотному напрямку після кожного циклу. repeatCount = .infinity створює нескінченний цикл «дихаючого» ефекту. speed = 1,0 — нормальна швидкість; прискорте анімацію вдвічі значенням 2,0.
SwiftUI TimelineView (iOS 15+) надає доступ до системної часової шкали для спеціальної анімації. View оновлюється при кожному тіку scheduler.
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 з .periodic scheduler генерує тіки кожні 0,1 с. У замиканні context.date — поточний час, який перетворюється на фазу синусоїди. Розмір кола пульсує з частотою 1 Гц. TimelineView — єдиний спосіб створити анімацію з власною часовою шкалою в SwiftUI без Animation API.
ValueAnimator з повним керуванням часовою шкалою: затримка, тривалість, повторення. Метод setCurrentPlayTime дозволяє стрибати по часовій шкалі.
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 мс — затримка перед стартом. repeatMode = REVERSE повертає анімацію до початкового значення. animatedFraction дає прогрес від 0 до 1 з урахуванням repeatMode. addUpdateListener спрацьовує кожен кадр (зазвичай 16 мс). Для призупинення використовуйте pause() / resume().
AnimatorSet дозволяє створювати складні часові шкали з кількох анімацій. У прикладі — послідовне виконання з перекриттям (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 запускає всі анімації паралельно, але кожна має свій startDelay — це створює stagger-ефект: fadeIn стартує першим (0 мс), slideUp через 100 мс, scale через 200 мс. Підсумкова часова шкала: 0 — 100 — 200 — 600 мс. Для послідовного виконання використовуйте playSequentially.
Часто задавані питання
Animator — абстрактний базовий клас для всіх анімацій. ValueAnimator — його підклас, що анімує числові значення без прив'язки до об'єкта. ObjectAnimator (нащадок ValueAnimator) анімує конкретну властивість об'єкта (наприклад, alpha або translationY). Для часової шкали використовуйте ValueAnimator як універсальний таймер з колбеком через addUpdateListener.
Використовуйте системні годинники (CACurrentMediaTime в iOS, System.nanoTime в Android) для розрахунку прогресу, а не кількість кадрів. Для мережевої синхронізації застосовуйте NTP-базований таймер з корекцією offset. iOS підтримує AVAudioSession для синхронізації аудіо-відео часової шкали з точністю до мілісекунд. Уникайте синхронізації по кадрах — частота кадрів варіюється від 30 до 120 fps.
startOffset — затримка перед початком анімації в мілісекундах. Використовується для каскаду та stagger-ефектів. repeatCount — кількість повторень: 0 = не повторюється, ValueAnimator.INFINITE = нескінченний цикл. repeatMode визначає напрямок: RESTART (скидання до початку) або REVERSE (зворотний хід). В iOS startOffset називається beginTime (в комбінації з CACurrentMediaTime), repeatCount — однойменна властивість CABasicAnimation.
Material Design рекомендує 200–350 мс для інтерфейсних анімацій. Анімації коротші за 100 мс не сприймаються як анімація, довші за 500 мс — дратують. Для входу/виходу елементів: 200–300 мс. Для переміщення між екранами: 300–400 мс. Використовуйте коротші тривалості для дрібних елементів (100–200 мс) і довші для великих (400–500 мс).
Смикання (jank) виникає через: тривалі операції в головному потоці (блокують рендеринг), складну ієрархію View/Layer з overdraw, або неправильне використання часової шкали (тривалість не кратна vsync — 16ms для 60fps, 8ms для 120fps). Використовуйте Xcode Instruments Animations або Android Studio Profiler для виявлення пропущених кадрів. В Compose застосовуйте Modifier.drawWithCache для кешування складних відмальовок.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також