こんにちは。寺坂です。
複雑なアニメーションでも、突き詰めると
アニメーション == フレームの連続
になります。
一枚一枚のフレームをそのまま実装に落とし込むことができれば、
表現力はそのままに、シンプルなアプローチが可能となりますね。
今回はフレーム視点でアニメーションを整理し、iOSで使える CAKeyframeAnimation での実装を紹介します。
フレームから紐解く
右に移動、上に移動、大きくなる、消える、、、など、
様々な種類のアニメーションがありますが、
そのそれぞれに対して、
「変化フレーム」
「時間フレーム」
の2種類のフレームを考えながらアニメーションを整理します。
今回はアプリ版 GANMA! でお馴染みの
「ハートがふるえるアニメーション」
(以下、ハートアニメーション)
を例にして進めていきます。
変化フレーム
変化フレームは、素材に対してどういう変化を加えるかを表します。
例えば、右に移動、上に移動など、アニメーションに必要な動きを書き出します。
ハートアニメーションの変化を分解して時系列に並べると、
- 通常の大きさ
- 縦に短く、横に長くなる
- 横に短く、縦に長くなる
- 縦に短く、横に長くなる
- 横に短く、縦に長くなる
- 縦に短く、横に長くなる
- 横に短く、縦に長くなる
- 通常の大きさ
となります。
縦や横に、計8個の変化フレームがありますね。
時間フレーム
時間フレームは、変化フレームに対してセットするものです。
ハートアニメーション全体が「10個の時間フレーム」を持っているとして、
これらを先ほどの8個の変化フレームに割り振ります。
ハートアニメーションは最初ゆっくりと、後半は素早く動くので、
- 変化1~4 →ゆっくり
- 変化4~8 →素早く
と決めます。
ゆっくりと動かすには、時間を多く与えてあげればいいので、
フレームの概念に置き換えると、
- 変化1 →0フレーム使う →始まり位置
- 変化2 →2フレーム使う →2/10で切り替わる
- 変化3 →2フレーム使う →4/10で切り替わる
- 変化4 →2フレーム使う →6/10で切り替わる
- 変化5 →1フレーム使う →7/10で切り替わる
- 変化6 →1フレーム使う →8/10で切り替わる
- 変化7 →1フレーム使う →9/10で切り替わる
- 変化8 →1フレーム使う →終わり位置
となります。
表に整理すると
以上のことを表にまとめると、次のようになります。
あとはこの情報を実装に当てはめていくだけです。
- 形を変える → 縦横のscaleを変える
変化フレーム | 時間フレームの個数 | scale値: (x, y) | 速さ |
---|---|---|---|
通常の大きさ | 0 | (1.0, 1.0) | ゆっくり |
縦を縮めて、横を伸ばす | 0〜2 | (1.3, 0.7) | ゆっくり |
横を縮めて、縦を伸ばす | 2〜4 | (0.8, 1.2) | ゆっくり |
縦を縮めて、横を伸ばす | 3〜6 | (1.25, 0.85) | ゆっくり |
横を縮めて、縦を伸ばす | 7 | (0.93, 1.08) | 素早く |
縦を縮めて、横を伸ばす | 8 | (1.1, 0.9) | 素早く |
横を縮めて、縦を伸ばす | 9 | (0.98, 1.03) | 素早く |
通常の大きさ | 10 | (1.0, 1.0) | 素早く |
フレームを実装に当てはめる
ここではiOSで用意されている
「CAKeyframeAnimation」
を使います。
これはその名の通り、アニメーションをフレーム単位でコントロールします。
基本的な部分だけで簡単に動きを表現できるので、とても便利な代物です。
それでは早速実装に落としていきます。
まずは形を変化をさせたいので、keyPathを"transform" に指定します。
let anim = CAKeyframeAnimation(keyPath: "transform")
次に変化フレームを実装するため、scale値をセットします。
anim.values = [ NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(1.3, 0.7, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(0.8, 1.2, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(1.25, 0.85, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(0.93, 1.08, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(1.1, 0.9, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(0.98, 1.03, 1.0)), NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)) ]
最後に、上のscale値に対応させながら、時間フレームをセットしていきます。
anim.keyTimes = [ 0.0, 0.2, 0.4, 0.6, 0.7, 0.8, 0.9, 1.0 ]
durationとかアニメーションカーブを付け加えてまとめると、こうなります。
let anim = CAKeyframeAnimation(keyPath: "transform") anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) anim.repeatCount = .infinity anim.duration = 1.5 anim.values = [ NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)), // 0/10 NSValue(caTransform3D: CATransform3DMakeScale(1.3, 0.7, 1.0)), // 2/10 NSValue(caTransform3D: CATransform3DMakeScale(0.8, 1.2, 1.0)), // 4/10 NSValue(caTransform3D: CATransform3DMakeScale(1.25, 0.85, 1.0)), // 6/10 NSValue(caTransform3D: CATransform3DMakeScale(0.93, 1.08, 1.0)), // 7/10 NSValue(caTransform3D: CATransform3DMakeScale(1.1, 0.9, 1.0)), // 8/10 NSValue(caTransform3D: CATransform3DMakeScale(0.98, 1.03, 1.0)), // 9/10 NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)) // 10/10 ] anim.keyTimes = [ 0.0, 0.2, 0.4, 0.6, 0.7, 0.8, 0.9, 1.0 ]
これだけでハートアニメーションを実装できました。簡単ですね!
変化の種類が複数あるアニメーションの場合
移動しながら大きさを変えるなど、
いろんな種類のアニメーションを組み合わせる場合もあります。
その時は、変化の種類ごとにフレームを考えると整理できると思います。
CAKeyframeAnimationで言えば、
- サイズ変化用のCAKeyframeAnimation
- 位置移動用のCAKeyframeAnimation
- 色変化用のCAKeyframeAnimation
というように、それぞれの変化ごとにアニメーションを用意しておけば、最後にそれらをまとめて合成することができます。
// transaction開始 CATransaction.begin() // アニメーションを追加する layer.add(anim1, forKey: "opacity") layer.add(anim2, forKey: "moveY") layer.add(anim3, forKey: "moveX") CATransaction.setCompletionBlock { _ in // 終了時の処理 } // アニメーションスタート。ここが呼ばれるまでアニメーションはスタートしない CATransaction.commit()
最後に
以上になります。
「フレーム駆動設計」と銘打ってきましたが、実はCAKeyframeAnimationの紹介が主旨だったりします。
フレームを整理した上でCAKeyframeAnimationを使えば、とても簡単にアニメーションを実装できるので、
是非使ってみてください!
最後までお読みいただき、ありがとうございました。