Главное
Timeline — это модель времени анимации, которая определяет, когда анимация начинается, как долго длится и как ход времени преобразуется в изменение анимируемого параметра. В отличие от покадровой анимации (frame-by-frame), где каждый кадр задаётся вручную, timeline-анимация описывается параметрами: duration — общая длительность, startDelay — задержка перед стартом, repeatCount — количество повторений, и timingFunction — кривая скорости.
Временная шкала работает в нормализованном пространстве [0, 1] — local time или progress. Нулевое значение соответствует началу анимации, единица — завершению. Easing-функция преобразует линейный local time в фактический прогресс анимации. Если repeatCount > 1, timeline циклически сбрасывается (RESTART) или инвертируется (REVERSE). В iOS CAMediaTiming добавляет параметры speed (скорость воспроизведения, >1 ускоряет) и timeOffset (сдвиг временной шкалы). В Android Animator использует setCurrentPlayTime() для прямого управления позицией на временной шкале. По данным Material Design, анимации короче 100 мс не воспринимаются как анимация, а длиннее 500 мс — начинают раздражать. Оптимальная длительность для интерфейсных анимаций — 200–350 мс.
В iOS управление временной шкалой анимации осуществляется через протокол CAMediaTiming, который реализуют CAAnimation и CALayer. Он предоставляет свойства: duration (длительность в секундах), beginTime (время старта относительно родительского timeline), repeatCount (количество повторений), repeatDuration (общая длительность повторений), autoreverses (автообратный ход), fillMode (поведение до/после анимации), speed (скорость воспроизведения) и timeOffset (сдвиг по шкале).
CACurrentMediaTime() — функция, возвращающая абсолютное время в секундах от последней перезагрузки устройства. Используется как базовая временная метка для синхронизации нескольких анимаций: установите beginTime = CACurrentMediaTime() + offset для запуска группы анимаций с точной задержкой. CAMediaTiming также поддерживает иерархию timeline — родительский слой может ускорять/замедлять все дочерние анимации через свойство speed. В UIKit UIViewPropertyAnimator (iOS 10+) абстрагирует временную шкалу через свойства duration, delay и колбэки startAnimation/pauseAnimation/stopAnimation. В SwiftUI Animation структура предоставляет duration как часть спецификации анимации, но прямое управление временной шкалой скрыто — для кастомного timeline используйте TimelineView (iOS 15+) с собственным scheduler.
В Android управление временной шкалой централизовано в классе Animator и его наследниках (ValueAnimator, ObjectAnimator). Основные свойства timeline: duration (длительность в миллисекундах), startDelay (задержка перед стартом), repeatCount (количество повторений), repeatMode (RESTART или REVERSE). Метод setCurrentPlayTime(long) позволяет перемещаться по временной шкале в произвольную позицию.
ValueAnimator добавляет уровень абстракции: он генерирует значения от 0 до 1 (normalized time) на основе duration и передаёт их в TimeInterpolator. AnimatorSet позволяет комбинировать несколько Animator в единую временную шкалу с последовательным (playSequentially) или параллельным (playTogether) выполнением. В Jetpack Compose временная шкала управляется через AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) и keyframes (точки на временной шкале с кастомным easing для каждого отрезка). Compose AnimatedContent использует timeline для transitionSpec — разработчик описывает, как контент появляется/исчезает с временными сдвигами. По данным Android Performance, временная шкала анимации должна быть кратной 16 мс (vsync-цикл) для 60fps — неправильная длительность может вызывать пропуск кадров из-за несовпадения с refresh rate дисплея.
Временная шкала становится особенно важной при композиции нескольких анимаций. Три базовых паттерна: каскад — анимации запускаются последовательно со сдвигом (каждая следующая начинается через offset после предыдущей), параллель — все анимации запускаются одновременно с одинаковым timeline, и 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 обе платформы представили инструменты визуализации timeline: Xcode Animations Inspector и Android Studio Animations Timeline — они показывают фактическую временную шкалу и пропущенные кадры в реальном времени.
Пример анимации с кастомным 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 раза значением 2.0.
SwiftUI TimelineView (iOS 15+) предоставляет доступ к системному timeline для кастомной анимации. 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). Для timeline используйте ValueAnimator как универсальный таймер с колбэком через addUpdateListener.
Используйте системные часы (CACurrentMediaTime в iOS, System.nanoTime в Android) для расчёта прогресса, а не количество кадров. Для сетевой синхронизации применяйте NTP-based таймер с коррекцией offset. iOS поддерживает AVAudioSession для синхронизации аудио-видео timeline с точностью до миллисекунд. Избегайте синхронизации по кадрам — частота кадров варьируется от 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, или неправильного использования timeline (длительность не кратна vsync — 16ms для 60fps, 8ms для 120fps). Используйте Xcode Instruments Animations или Android Studio Profiler для обнаружения пропущенных кадров. В Compose применяйте Modifier.drawWithCache для кэширования сложных отрисовок.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также