Timeline — основи, управление на времето за анимация в приложенията

Автор: IT Sectr Публикувано: 2026-03-02 Време за четене: 8 мин
Timeline (времева линия) — фундаментална концепция на анимацията, която определя колко дълго продължава анимацията, в какъв ред се изпълняват нейните етапи и как времето се отразява върху напредъка. В разработката на мобилни приложения timeline се управлява чрез продължителност (duration), забавяне (delay), повторение (repeat) и времеви маркери. В iOS анимационният timeline е имплементиран чрез CACurrentMediaTime и CAMediaTiming, в Android — чрез ValueAnimator и Animator с параметри duration, startDelay и repeatCount. Според Apple Документация, точното управление на времевата линия е критично важно за синхронизирането на анимации: десинхронизация дори с 16 ms (един кадър при 60fps) се възприема от потребителя като дръпване. В IT Sectr стандартизирахме използването на timeline чрез ValueAnimator в Android и CABasicAnimation с CAMediaTiming в iOS — това осигурява еднообразно управление на времето във всички проекти.

Основни

  • Timeline — концепция за управление на времето на анимация: duration, delay, repeat, отразяване на времето върху напредъка.
  • CAMediaTiming — протокол на iOS Core Animation за управление на timeline: duration, beginTime, repeatCount, speed, timeOffset.
  • ValueAnimator — централният клас в Android за работа с времевата линия на анимация чрез duration, startDelay, repeatCount.
  • CACurrentMediaTime — функция на iOS за получаване на абсолютно време в секунди, използва се за синхронизация.
  • TimeInterval — тип данни в iOS (Double), представляващ времеви интервал за анимационни изчисления.

Какво е Timeline?

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 ms не се възприемат като анимация, а по-дълги от 500 ms — започват да дразнят. Оптималната продължителност за интерфейсни анимации е 200–350 ms.

Timeline в iOS: CAMediaTiming и CACurrentMediaTime

В 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.

Timeline в Android: Animator и duration

В 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 ms (vsync цикъл) за 60fps — неправилната продължителност може да доведе до загуба на кадри поради несъответствие с честотата на опресняване на екрана.

Композиция на анимации във времето

Времевата линия става особено важна при композицията на няколко анимации. Три основни модела: каскада — анимациите се стартират последователно с отместване (всяка следваща започва след 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 — те показват действителната времева линия и пропуснатите кадри в реално време.

Примери за код

iOS: CABasicAnimation с CAMediaTiming

Пример за анимация с персонализирана timeline: забавяне 0.3 сек, продължителност 0.6 сек, autoreverse и безкрайно повторение. CAMediaTiming управлява всички времеви параметри.

swift
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 ms. autoreverses = true кара анимацията да се движи в обратна посока след всеки цикъл. repeatCount = .infinity създава безкраен цикъл на ефекта "дишане". speed = 1.0 — нормална скорост; ускорете анимацията 2 пъти със стойност 2.0.

iOS: TimelineView за персонализирана времева линия

SwiftUI TimelineView (iOS 15+) предоставя достъп до системната timeline за персонализирана анимация. Изгледът се актуализира при всеки тик на scheduler.

swift
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 Hz. TimelineView е единственият начин за създаване на анимация със собствена времева линия в SwiftUI без Animation API.

Android: ValueAnimator с персонализирана timeline

ValueAnimator с пълно управление на времевата линия: забавяне, продължителност, повторение. Методът setCurrentPlayTime позволява скачане по времевата линия.

kotlin
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 — забавяне преди старта. repeatMode = REVERSE връща анимацията към началната стойност. animatedFraction дава напредък от 0 до 1, като взема предвид repeatMode. addUpdateListener се извиква на всеки кадър (обикновено 16 ms). За пауза използвайте pause() / resume().

Android: AnimatorSet за композиция на timeline

AnimatorSet позволява създаване на сложни времеви линии от няколко анимации. В примера — последователно изпълнение с припокриване (stagger).

kotlin
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 ms), slideUp след 100 ms, scale след 200 ms. Резултатна времева линия: 0 — 100 — 200 — 600 ms. За последователно изпълнение използвайте playSequentially.

Често задавани въпроси

Каква е разликата между Animator и ValueAnimator в Android?

Animator — абстрактен базов клас за всички анимации. ValueAnimator — негов подклас, който анимира числови стойности без обвързване с обект. ObjectAnimator (наследник на ValueAnimator) анимира конкретно свойство на обект (например alpha или translationY). За timeline използвайте ValueAnimator като универсален таймер с обратно извикване чрез addUpdateListener.

Как да синхронизирам анимации на различни устройства?

Използвайте системни часовници (CACurrentMediaTime в iOS, System.nanoTime в Android) за изчисляване на напредъка, а не броя на кадрите. За мрежова синхронизация приложете NTP-базиран таймер с корекция на offset. iOS поддържа AVAudioSession за синхронизация на аудио-видео timeline с точност до милисекунди. Избягвайте синхронизация по кадри — честотата на кадрите варира от 30 до 120 fps.

Какво представляват startOffset и repeatCount в Animator?

startOffset — забавяне преди началото на анимацията в милисекунди. Използва се за каскада и stagger ефекти. repeatCount — брой повторения: 0 = без повторение, ValueAnimator.INFINITE = безкраен цикъл. repeatMode определя посоката: RESTART (нулиране до началото) или REVERSE (обратно движение). В iOS startOffset се нарича beginTime (в комбинация с CACurrentMediaTime), repeatCount — едноименното свойство на CABasicAnimation.

Каква е оптималната продължителност на анимация?

Material Design препоръчва 200–350 ms за интерфейсни анимации. Анимациите по-къси от 100 ms не се възприемат като анимация, по-дълги от 500 ms — дразнят. За влизане/излизане на елементи: 200–300 ms. За преходи между екрани: 300–400 ms. Използвайте по-кратки продължителности за малки елементи (100–200 ms) и по-дълги за големи (400–500 ms).

Защо анимацията дръпва по време на възпроизвеждане?

Дръпването (jank) възниква поради: дълги операции в основната нишка (блокират рендерирането), сложна йерархия View/Layer с overdraw или неправилно използване на timeline (продължителността не е кратна на vsync — 16ms за 60fps, 8ms за 120fps). Използвайте Xcode Instruments Animations или Android Studio Profiler за откриване на пропуснати кадри. В Compose приложете Modifier.drawWithCache за кеширане на сложни рисунки.

Резюме

  • Timeline — модел на времето на анимация с параметри duration, startDelay, repeatCount, repeatMode, определящ хода на анимацията.
  • Протоколът iOS CAMediaTiming управлява времевата линия чрез duration, beginTime, speed, timeOffset, repeatCount, fillMode.
  • Android Animator и ValueAnimator предоставят duration, startDelay, repeatCount, repeatMode и setCurrentPlayTime за директно управление.
  • AnimatorSet в Android и CAAnimationGroup в iOS позволяват комбиниране на анимации в една времева линия.
  • TimelineView в SwiftUI (iOS 15+) предоставя достъп до системния scheduler за персонализирани времеви линии без Animation API.
  • CACurrentMediaTime() в iOS и System.nanoTime в Android — основни времеви маркери за синхронизация на анимации.
  • Оптималната продължителност на интерфейсни анимации — 200-350 ms според препоръките на Material Design и HIG.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също