Pontos Principais
Timeline é um modelo de tempo de animação que define quando uma animação começa, quanto tempo dura e como a progressão do tempo é transformada em mudanças do parâmetro animado. Ao contrário da animação quadro a quadro (frame-by-frame), onde cada quadro é definido manualmente, a animação por linha do tempo é descrita por parâmetros: duration — duração total, startDelay — atraso antes do início, repeatCount — número de repetições, e timingFunction — a curva de velocidade.
A linha do tempo opera em um espaço normalizado [0, 1] — tempo local ou progresso. O valor zero corresponde ao início da animação, um à conclusão. Uma função de easing (suavização) converte o tempo local linear em progresso real da animação. Se repeatCount > 1, a linha do tempo cicla (RESTART) ou inverte (REVERSE). No iOS, CAMediaTiming adiciona speed (taxa de reprodução, >1 acelera) e timeOffset (deslocamento da linha do tempo). No Android, Animator usa setCurrentPlayTime() para controle direto da posição na linha do tempo. De acordo com Material Design, animações com menos de 100 ms não são percebidas como animações, enquanto as com mais de 500 ms começam a irritar. A duração ideal para animações de interface é de 200–350 ms.
No iOS, o gerenciamento da linha do tempo da animação é feito através do protocolo CAMediaTiming, implementado por CAAnimation e CALayer. Ele fornece as propriedades: duration (em segundos), beginTime (tempo de início relativo à linha do tempo pai), repeatCount (número de repetições), repeatDuration (duração total das repetições), autoreverses (reversão automática), fillMode (comportamento antes/depois da animação), speed (taxa de reprodução) e timeOffset (deslocamento da linha do tempo).
CACurrentMediaTime() é uma função que retorna o tempo absoluto em segundos desde a última reinicialização do dispositivo. É usada como carimbo de tempo base para sincronizar múltiplas animações: defina beginTime = CACurrentMediaTime() + offset para iniciar um grupo de animações com atraso preciso. CAMediaTiming também suporta hierarquia de linha do tempo — uma camada pai pode acelerar/desacelerar todas as animações filhas através da propriedade speed. No UIKit, UIViewPropertyAnimator (iOS 10+) abstrai a linha do tempo através das propriedades duration, delay e callbacks startAnimation/pauseAnimation/stopAnimation. No SwiftUI, a estrutura Animation fornece duration como parte da especificação da animação, mas o controle direto da linha do tempo está oculto — para linha do tempo personalizada, use TimelineView (iOS 15+) com um scheduler personalizado.
No Android, o gerenciamento da linha do tempo é centralizado na classe Animator e suas subclasses (ValueAnimator, ObjectAnimator). As principais propriedades da linha do tempo são: duration (em milissegundos), startDelay (atraso antes do início), repeatCount (número de repetições), repeatMode (RESTART ou REVERSE). O método setCurrentPlayTime(long) permite navegar pela linha do tempo para qualquer posição arbitrária.
ValueAnimator adiciona uma camada de abstração: gera valores de 0 a 1 (tempo normalizado) com base na duration e os passa para TimeInterpolator. AnimatorSet permite combinar vários Animators em uma única linha do tempo com execução sequencial (playSequentially) ou paralela (playTogether). No Jetpack Compose, a linha do tempo é gerenciada através de AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) e keyframes (pontos na linha do tempo com easing personalizado para cada segmento). Compose AnimatedContent usa uma linha do tempo para transitionSpec — o desenvolvedor descreve como o conteúdo aparece/desaparece com deslocamentos temporais. De acordo com Android Performance, a linha do tempo da animação deve ser um múltiplo de 16 ms (ciclo vsync) para 60fps — duração incorreta pode causar queda de quadros devido à incompatibilidade com a taxa de atualização da tela.
A linha do tempo torna-se especialmente importante ao compor múltiplas animações. Três padrões básicos: cascata — as animações iniciam sequencialmente com um deslocamento (cada próxima começa após um offset da anterior), paralelo — todas as animações começam simultaneamente com a mesma linha do tempo, e escalonado (stagger) — as animações começam com sobreposição (a segunda começa antes da primeira terminar).
No iOS, a cascata é implementada através de beginTime de cada CAAnimation = beginTime anterior + duration anterior. Para escalonamento, CAAnimationGroup é usado com diferentes valores de beginTime em animações aninhadas. No Android, AnimatorSet suporta playSequentially e playTogether; o escalonamento é criado definindo startDelay para cada Animator. No Compose, AnimatedVisibility com enter/exitTransition usa escalonamento na variante staggerChildren para aparecimento em cascata de itens de lista. Na WWDC e Google I/O 2024, ambas as plataformas apresentaram ferramentas de visualização de linha do tempo: Xcode Animations Inspector e Android Studio Animations Timeline — eles mostram a linha do tempo real e quadros perdidos em tempo real.
Um exemplo de animação com linha do tempo personalizada: atraso de 0,3 s, duração de 0,6 s, autoreverse e repetição infinita. CAMediaTiming controla todos os parâmetros temporais.
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 fornece um atraso de 300 ms. autoreverses = true faz a animação executar em reverso após cada ciclo. repeatCount = .infinity cria um loop infinito do efeito “respiração”. speed = 1,0 — velocidade normal; acelere a animação 2x com um valor de 2,0.
SwiftUI TimelineView (iOS 15+) fornece acesso à linha do tempo do sistema para animação personalizada. A View atualiza a cada tick do 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 com scheduler .periodic gera ticks a cada 0,1 s. No closure, context.date é a hora atual, que é convertida em uma fase de onda senoidal. O tamanho do círculo pulsa a uma frequência de 1 Hz. TimelineView é a única maneira de criar uma animação com sua própria linha do tempo no SwiftUI sem a API Animation.
ValueAnimator com controle total da linha do tempo: atraso, duração, repetição. O método setCurrentPlayTime permite saltar ao longo da linha do tempo.
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 — atraso antes do início. repeatMode = REVERSE retorna a animação ao valor inicial. animatedFraction dá o progresso de 0 a 1 considerando repeatMode. addUpdateListener dispara a cada quadro (normalmente 16 ms). Use pause() / resume() para pausar.
AnimatorSet permite criar linhas do tempo complexas a partir de múltiplas animações. O exemplo demonstra execução sequencial com sobreposição (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 inicia todas as animações em paralelo, mas cada uma tem seu próprio startDelay — isso cria um efeito escalonado: fadeIn começa primeiro (0 ms), slideUp após 100 ms, scale após 200 ms. A linha do tempo resultante: 0 — 100 — 200 — 600 ms. Para execução sequencial, use playSequentially.
Perguntas Frequentes
Animator é uma classe base abstrata para todas as animações. ValueAnimator é sua subclasse que anima valores numéricos sem vincular a um objeto. ObjectAnimator (subclasse de ValueAnimator) anima uma propriedade específica de um objeto (por exemplo, alpha ou translationY). Para linha do tempo, use ValueAnimator como um temporizador universal com um callback via addUpdateListener.
Use relógios do sistema (CACurrentMediaTime no iOS, System.nanoTime no Android) para calcular o progresso, não a contagem de quadros. Para sincronização em rede, use um temporizador baseado em NTP com correção de offset. O iOS suporta AVAudioSession para sincronização de linha do tempo de áudio-vídeo com precisão de milissegundos. Evite sincronização baseada em quadros — as taxas de quadros variam de 30 a 120 fps.
startOffset é o atraso antes do início da animação em milissegundos. Usado para efeitos de cascata e escalonamento. repeatCount é o número de repetições: 0 = sem repetição, ValueAnimator.INFINITE = loop infinito. repeatMode determina a direção: RESTART (reiniciar do início) ou REVERSE (direção reversa). No iOS, startOffset é chamado beginTime (em combinação com CACurrentMediaTime), repeatCount é a mesma propriedade de CABasicAnimation.
Material Design recomenda 200–350 ms para animações de interface. Animações com menos de 100 ms não são percebidas como animações, enquanto as com mais de 500 ms são irritantes. Para entrada/saída de elementos: 200–300 ms. Para transições entre telas: 300–400 ms. Use durações mais curtas para elementos pequenos (100–200 ms) e mais longas para elementos grandes (400–500 ms).
A trepidação (jank) ocorre devido a: operações de longa duração na thread principal (bloqueiam a renderização), hierarquia complexa de View/Layer com overdraw, ou uso incorreto da linha do tempo (duração não múltipla de vsync — 16ms para 60fps, 8ms para 120fps). Use Xcode Instruments Animations ou Android Studio Profiler para detectar quadros perdidos. No Compose, use Modifier.drawWithCache para armazenar em cache desenhos complexos.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também