Основни
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.
В 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 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 — те показват действителната времева линия и пропуснатите кадри в реално време.
Пример за анимация с персонализирана 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 ms. autoreverses = true кара анимацията да се движи в обратна посока след всеки цикъл. repeatCount = .infinity създава безкраен цикъл на ефекта "дишане". speed = 1.0 — нормална скорост; ускорете анимацията 2 пъти със стойност 2.0.
SwiftUI TimelineView (iOS 15+) предоставя достъп до системната timeline за персонализирана анимация. Изгледът се актуализира при всеки тик на 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 Hz. 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 ms — забавяне преди старта. repeatMode = REVERSE връща анимацията към началната стойност. animatedFraction дава напредък от 0 до 1, като взема предвид repeatMode. addUpdateListener се извиква на всеки кадър (обикновено 16 ms). За пауза използвайте 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 ms), slideUp след 100 ms, scale след 200 ms. Резултатна времева линия: 0 — 100 — 200 — 600 ms. За последователно изпълнение използвайте playSequentially.
Често задавани въпроси
Animator — абстрактен базов клас за всички анимации. ValueAnimator — негов подклас, който анимира числови стойности без обвързване с обект. ObjectAnimator (наследник на ValueAnimator) анимира конкретно свойство на обект (например alpha или translationY). За timeline използвайте ValueAnimator като универсален таймер с обратно извикване чрез addUpdateListener.
Използвайте системни часовници (CACurrentMediaTime в iOS, System.nanoTime в Android) за изчисляване на напредъка, а не броя на кадрите. За мрежова синхронизация приложете NTP-базиран таймер с корекция на 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 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 за кеширане на сложни рисунки.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също