CAAnimationでCALayerを動かす
CALayerは単純な位置の移動だけなら、
layer.position = CGPointMake(x, y);
というようにすれば、アニメーションとともに移動してくれます。position以外にも、opacityの変更などでもアニメーションが発生します。
しかし、アニメーションの時間を細かく決めたいといった場合にはCATransactionやCAAnimationを使う必要があります。
CATransactionを使った例。
CAMediaTimingFunction *tf;
tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[CATransaction begin];
[CATransaction setAnimationDuration:1.0f];
[CATransaction setAnimationTimingFunction:tf];
layer.position = CGPointMake(x, y);
[CATransaction commit];
setAnimationDurationでアニメーションの時間を決め、setAnimationTimingFunctionでアニメーションの速度の変化を決めています。今回はkCAMediaTimingFunctionEaseOutを設定していて、これは時間とともにアニメーションの速度がゆっくりになっていくものです。
CATransactionはかなり簡便に使えますが、アニメーションの終了通知を受け取ることができません(iOS4.0以降であれば、blockを使えばできます)。そこで、今度はCAAnimation(CABasicAnimation)を使ってみます。
- (void)moveLayer { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.delegate = self; animation.fromValue = [NSValue valueWithCGPoint:layer.position]; animation.toValue = [NSValue valueWithCGPoint:CGPointMake(x, y)]; animation.duration = 1.0f; [layer addAnimation:animation forKey:@"changePosition"]; } - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { NSLog(@"layer moved"); }
1秒間かけてlayerを元の位置から(x, y)に移動させています。また、デリゲートで終了通知を受け取っています。
これは一見ちゃんと動くように見えますが、実はCAAnimationの場合はlayerのpositionを書き換えず、アニメーションの終了と同時にlayerが元の位置に戻ってしまいます。そこで、移動先で停止させる方法を2つ紹介します。
1つ目。removedOnCompletionとfillModeを使う方法。
- (void)moveLayer { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.delegate = self; animation.fromValue = [NSValue valueWithCGPoint:layer.position]; animation.toValue = [NSValue valueWithCGPoint:CGPointMake(x, y)]; animation.duration = 1.0f; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; [layer addAnimation:animation forKey:@"changePosition"]; } - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { NSLog("moved to x: %lf, y: %lf", layer.position.x, layer.position.y); [CATransaction begin]; [CATransaction setAnimationDuration:0.0f]; layer.position = CGPointMake(x, y); [CATransaction commit]; }
removedOnCompletionとfillModeを上記のように設定することで、layerをアニメーションの終了位置に固定することが出来ます。しかし、デリゲートメソッドで確認すると分かるのですが、layerのposition自体は書き換わっていません。そのため、改めてposition値を書き換える作業が必要になります。position値の書き換え時には再びアニメーションが動いてしまうので、CATransactionを利用してアニメーション時間を0秒にしています。あまり良いコードとは思えないですね。
2つ目。先にposition値を書き換えてからアニメーションを動かす。
- (void)moveLayer { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.delegate = self; animation.fromValue = [NSValue valueWithCGPoint:layer.position]; animation.toValue = [NSValue valueWithCGPoint:CGPointMake(x, y)]; animation.duration = 1.0f; layer.position = CGPointMake(x, y); [layer addAnimation:animation forKey:@"changePosition"]; } - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { NSLog("moved to x: %lf, y: %lf", layer.position.x, layer.position.y); }
発想の転換です。元の位置に戻ってしまうのであれば「元の位置を移動先に」してからアニメーションを動かせばいいわけです。
大量のアニメーションを扱うのであればCore Animationを使うほうが良いのですが、たった1個動かすという程度であれば、Cocoa(UIView)の機能を使ったほうが楽です。分かってしまえばCore Animationも特別複雑には感じませんが。
デベロッパドキュメントにCore AnimationのProgramming GuideとCook Bookがあるので、詳しく知りたいのであればそれを読むべきですね。私もちゃんと読むことにします。