重要なポイント
Timelineはアニメーションの時間モデルであり、アニメーションの開始時期、持続時間、時間の経過がアニメーションパラメータの変化にどのように変換されるかを定義します。各フレームを手動で設定するフレームバイフレームアニメーションとは異なり、タイムラインアニメーションはパラメータで記述されます:duration — 総持続時間、startDelay — 開始前の遅延、repeatCount — 繰り返し回数、timingFunction — 速度曲線。
タイムラインは正規化空間[0, 1] — ローカル時間または進行度で動作します。ゼロ値はアニメーションの開始、1は完了に対応します。イージング関数は線形のローカル時間を実際のアニメーション進行度に変換します。repeatCount > 1の場合、タイムラインは循環(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(親タイムラインに対する開始時間)、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+)を使用してください。
Androidでは、タイムライン管理はAnimatorクラスとそのサブクラス(ValueAnimator、ObjectAnimator)に集中しています。主なタイムラインプロパティは次のとおりです:duration(ミリ秒)、startDelay(開始前の遅延)、repeatCount(繰り返し回数)、repeatMode(RESTARTまたはREVERSE)。setCurrentPlayTime(long)メソッドを使用すると、タイムライン上の任意の位置に移動できます。
ValueAnimatorは抽象化レイヤーを追加します:durationに基づいて0から1(正規化時間)までの値を生成し、TimeInterpolatorに渡します。AnimatorSetを使用すると、複数のAnimatorを順次(playSequentially)または並列(playTogether)実行で単一のタイムラインに結合できます。Jetpack Composeでは、タイムラインはAnimationSpecを通じて管理されます:tween(durationMillis、delayMillis、easing)、spring(dampingRatio、stiffness)、keyframes(各セグメントのカスタムeasingを持つタイムラインポイント)。Compose AnimatedContentはtransitionSpecにタイムラインを使用します。開発者は時間オフセットでコンテンツの表示/非表示方法を記述します。Android Performanceによると、60fpsの場合、アニメーションタイムラインは16 ms(vsyncサイクル)の倍数である必要があります。持続時間が正しくないと、ディスプレイのリフレッシュレートとの不一致によりフレームドロップが発生する可能性があります。
複数のアニメーションを構成する際、タイムラインは特に重要になります。3つの基本パターン:カスケード — アニメーションがオフセット付きで順次開始(各次のアニメーションは前のアニメーションからオフセット後に開始)、並列 — すべてのアニメーションが同じタイムラインで同時に開始、スターガー(stagger) — アニメーションが重なって開始(2つ目は1つ目が完了する前に開始)。
iOSでは、カスケードは各CAAnimationのbeginTime = 前のbeginTime + 前のdurationによって実装されます。スターガーには、ネストされたアニメーションで異なるbeginTime値を持つCAAnimationGroupが使用されます。Androidでは、AnimatorSetはplaySequentiallyとplayTogetherをサポートし、各AnimatorにstartDelayを設定してスターガーを作成します。Composeでは、AnimatedVisibilityとenter/exitTransitionがstaggerChildrenバリアントでスターガーを使用して、リストアイテムのカスケード表示を実現します。WWDCとGoogle I/O 2024では、両プラットフォームがタイムライン可視化ツールを発表しました:Xcode Animations InspectorとAndroid Studio Animations Timelineは、実際のタイムラインとドロップフレームをリアルタイムで表示します。
カスタムタイムラインを使用したアニメーションの例:0.3秒の遅延、0.6秒の持続時間、自動反転、無限繰り返し。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.0の値でアニメーションを2倍に加速します。
SwiftUI TimelineView(iOS 15+)は、カスタムアニメーションのためのシステムタイムラインへのアクセスを提供します。ビューはスケジューラの各ティックで更新されます。
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スケジューラは0.1秒ごとにティックを生成します。クロージャ内で、context.dateは現在時刻であり、正弦波の位相に変換されます。円のサイズは1 Hzの周波数で脈動します。TimelineViewは、Animation APIなしでSwiftUIで独自のタイムラインを持つアニメーションを作成する唯一の方法です。
完全なタイムライン制御を備えた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はrepeatModeを考慮して0から1までの進行度を提供します。addUpdateListenerは毎フレーム(通常16 ms)で発火します。一時停止にはpause() / resume()を使用します。
AnimatorSetを使用すると、複数のアニメーションから複雑なタイムラインを作成できます。この例では、オーバーラップ(スターガー)を伴う順次実行を示しています。
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があります。これによりスターガー効果が生まれます:fadeInが最初に開始(0 ms)、slideUpが100 ms後、scaleが200 ms後。結果のタイムライン:0 — 100 — 200 — 600 ms。順次実行にはplaySequentiallyを使用します。
よくある質問
Animatorはすべてのアニメーションの抽象基底クラスです。ValueAnimatorはそのサブクラスで、オブジェクトにバインドせずに数値をアニメーション化します。ObjectAnimator(ValueAnimatorのサブクラス)は、オブジェクトの特定のプロパティ(alphaやtranslationYなど)をアニメーション化します。タイムラインには、addUpdateListenerを介したコールバック付きの汎用タイマーとしてValueAnimatorを使用します。
フレーム数ではなく、システムクロック(iOSのCACurrentMediaTime、AndroidのSystem.nanoTime)を使用して進行度を計算します。ネットワーク同期には、オフセット補正付きのNTPベースのタイマーを使用します。iOSは、ミリ秒精度のオーディオビデオタイムライン同期のためにAVAudioSessionをサポートしています。フレームベースの同期は避けてください。フレームレートは30〜120 fpsまで変化します。
startOffsetは、ミリ秒単位のアニメーション開始前の遅延です。カスケード効果やスターガー効果に使用されます。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階層、または不適切なタイムライン使用(持続時間がvsyncの倍数でない — 60fpsでは16ms、120fpsでは8ms)。ドロップフレームを検出するには、Xcode Instruments AnimationsまたはAndroid Studio Profilerを使用します。Composeでは、Modifier.drawWithCacheを使用して複雑な描画をキャッシュします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。