CABasicAnimationとの連続アニメーションを強制
-
16-09-2019 - |
質問
私は、モデル内の火災は、特定のプロパティが変更通知を持っています。結果として、特定のビュー・オブジェクトの選択は、それに応じてビューの位置を変更する通知をキャッチします。
の通知は(ウィンドウの標準的なステップサイズによって常に垂直方向または水平方向と常に)特定の方向に移動するウィンドウのビューを引き起こします。ユーザーアクションが次々に発射するいくつかの通知を引き起こすことが可能です。例えば、3つの通知は三つのステップダウンビューを移動させるために送信することができる、2つの以上の通知が正しい二段階に視野を移動させるために送信することができる。
問題は、私はアニメーションを実行すると、彼らは連続して発生しないということです。私はしたいもののので、前の例では、ビューは3つのスペースの下にゆっくりと移動させた後、通知の結果として、二つの空間の上に移動し、代わりにそれは新しい位置に斜めに移動してしまいます。
ここに私の2つのセレクタのためのコードである(モデル内の現在の情報に基づいてplacePlayerビューの位置を設定することに注意):
- (void)moveEventHandler: (NSNotification *) notification
{
[self placePlayer];
CABasicAnimation* moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
moveAnimation.duration = 3;
moveAnimation.fillMode = kCAFillModeForwards; // probably not necessary
moveAnimation.removedOnCompletion = NO; // probably not necessary
[[self layer] addAnimation:moveAnimation forKey:@"animatePosition"];
}
一度にステップ・バイ・ステップではなく、すべてを実行するために、この方法力アニメーションに複数の呼び出しを作成する方法上の任意の提案?感謝!!
解決 2
私が実際にキューを使用しない実装ソリューション。ここではかなり完全な記述があります:
このは、すべてのPlayerViewというビューのクラスで行われます。ヘッダにIは、以下のものが挙げられる:
#import "NSMutableArray+QueueAdditions.h"
@interface PlayerView : UIImageView {
Player* representedPlayer; // The model object represented by the view
NSMutableArray* actionQueue; // An array used as a queue for the actions
bool animatingPlayer; // Notes if the player is in the middle of an animation
bool stoppingAnimation; // Notes if all animations should be stopped (e.g., for re-setting the game)
CGFloat actionDuration; // A convenient way for me to change the duration of all animations
// ... Removed other variables in the class (sound effects, etc) not needed for this example
}
// Notifications
+ (NSString*) AnimationsDidStopNotification;
@property (nonatomic, retain) Player* representedPlayer;
@property (nonatomic, retain, readonly) NSMutableArray* actionQueue;
@property (nonatomic, assign) CGFloat actionDuration;
@property (nonatomic, assign) bool animatingPlayer;
@property (nonatomic, assign) bool stoppingAnimation;
// ... Removed other properties in the class not need for this example
- (void)placePlayer; // puts view where needed (according to the model) without animation
- (void)moveEventHandler:(NSNotification *) notification; // handles events when the player moves
- (void)rotateEventHandler:(NSNotification *) notification; // handles events when the player rotates
// ... Removed other action-related event handles not needed for this example
// These methods actually perform the proper animations
- (void) doMoveAnimation:(CGRect) nextFrame;
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection;
// ... Removed other action-related methods not needed for this example
// Handles things when each animation stops
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void*)context;
// Forces all animations to stop
- (void) stopAnimation;
@end
はさておき、NSMutableArrayの+ QueueAdditions.h /メートルでQueueAdditionsカテゴリは次のようになります。
@interface NSMutableArray (QueueAdditions)
- (id)popObject;
- (void)pushObject:(id)obj;
@end
@implementation NSMutableArray (QueueAdditions)
- (id)popObject
{
// nil if [self count] == 0
id headObject = [self objectAtIndex:0];
if (headObject != nil) {
[[headObject retain] autorelease]; // so it isn't dealloc'ed on remove
[self removeObjectAtIndex:0];
}
return headObject;
}
- (void)pushObject:(id)obj
{
[self addObject: obj];
}
@end
次に、PlayerViewの実装では、私は以下の持ってます:
#import "PlayerView.h"
#import <QuartzCore/QuartzCore.h>
@implementation PlayerView
@synthesize actionQueue;
@synthesize actionDuration;
@synthesize animatingPlayer;
@synthesize stoppingAnimation;
// ... Removed code not needed for this example (init to set up the view's image, sound effects, actionDuration, etc)
// Name the notification to send when animations stop
+ (NSString*) AnimationsDidStopNotification
{
return @"PlayerViewAnimationsDidStop";
}
// Getter for the representedPlayer property
- (Player*) representedPlayer
{
return representedPlayer;
}
// Setter for the representedPlayer property
- (void)setRepresentedPlayer:(Player *)repPlayer
{
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[representedPlayer release];
}
if (repPlayer == nil)
{
representedPlayer = nil;
// ... Removed other code not needed in this example
}
else
{
representedPlayer = [repPlayer retain];
if (self.actionQueue == nil)
{
actionQueue = [[NSMutableArray alloc] init];
}
[actionQueue removeAllObjects];
animatingPlayer = NO;
stoppingAnimation = NO;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(moveEventHandler:)
name:[Player DidMoveNotification]
object:repPlayer ];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(rotateEventHandler:)
name:[Player DidRotateNotification]
object:repPlayer ];
// ... Removed other addObserver actions and code not needed in this example
}
}
// ... Removed code not needed for this example
- (void) placePlayer
{
// Example not helped by specific code... just places the player where the model says it should go without animation
}
// Handle the event noting that the player moved
- (void) moveEventHandler: (NSNotification *) notification
{
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
// If we are in the middle of an animation, put information for the next animation in a dictionary
// and add that dictionary to the action queue.
// If we're not in the middle of an animation, just do the animation
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], @"nextFrame",
@"move", @"actionType",
@"player", @"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
animatingPlayer = YES; // note that we are now doing an animation
[self doMoveAnimation:nextFrame];
}
}
// Handle the event noting that the player rotated
- (void) rotateEventHandler: (NSNotification *) notification
{
// User info in the notification notes the direction of the rotation in a RotateDirection enum
NSDictionary* userInfo = [notification userInfo];
NSNumber* rotateNumber = [userInfo valueForKey:@"rotateDirection"];
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], @"nextFrame",
@"rotate", @"actionType",
rotateNumber, @"rotateDirectionNumber",
@"player", @"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
animatingPlayer = YES;
[self doRotateAnimation:nextFrame inDirection:direction];
}
}
// ... Removed other action event handlers not needed for this example
// Perform the actual animation for the move action
- (void) doMoveAnimation:(CGRect) nextFrame
{
[UIView beginAnimations:@"Move" context:NULL];
[UIView setAnimationDuration:actionDuration];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
self.frame = nextFrame;
[UIView commitAnimations];
}
// Perform the actual animation for the rotate action
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection
{
int iRot = +1;
if (rotateDirection == CounterClockwise)
{
iRot = -1;
}
[UIView beginAnimations:@"Rotate" context:NULL];
[UIView setAnimationDuration:(3*actionDuration)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
CGAffineTransform oldTransform = self.transform;
CGAffineTransform transform = CGAffineTransformRotate(oldTransform,(iRot*M_PI/2.0));
self.transform = transform;
self.frame = nextFrame;
[UIView commitAnimations];
}
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void *)context
{
// If we're stopping animations, clear the queue, put the player where it needs to go
// and reset stoppingAnimations to NO and note that the player is not animating
if (self.stoppingAnimation)
{
[actionQueue removeAllObjects];
[self placePlayer];
self.stoppingAnimation = NO;
self.animatingPlayer = NO;
}
else if ([actionQueue count] > 0) // there is an action in the queue, execute it
{
NSDictionary* actionInfo = (NSDictionary*)[actionQueue popObject];
NSString* actionTarget = (NSString*)[actionInfo valueForKey:@"actionTarget"];
NSString* actionType = (NSString*)[actionInfo valueForKey:@"actionType"];
// For actions to the player...
if ([actionTarget isEqualToString:@"player"])
{
NSValue* rectValue = (NSValue*)[actionInfo valueForKey:@"nextFrame"];
CGRect nextFrame = [rectValue CGRectValue];
if ([actionType isEqualToString:@"move"])
{
[self doMoveAnimation:nextFrame];
}
else if ([actionType isEqualToString:@"rotate"])
{
NSNumber* rotateNumber = (NSNumber*)[actionInfo valueForKey:@"rotateDirectionNumber"];
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
[self doRotateAnimation:nextFrame inDirection:direction];
}
// ... Removed code not needed for this example
}
else if ([actionTarget isEqualToString:@"cell"])
{
// ... Removed code not needed for this example
}
}
else // no more actions in the queue, mark the animation as done
{
animatingPlayer = NO;
[[NSNotificationCenter defaultCenter]
postNotificationName:[PlayerView AnimationsDidStopNotification]
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys: nil]];
}
}
// Make animations stop after current animation by setting stopAnimation = YES
- (void) stopAnimation
{
if (self.animatingPlayer)
{
self.stoppingAnimation = YES;
}
}
- (void)dealloc {
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
[representedPlayer release];
[actionQueue release];
// …Removed other code not needed for example
[super dealloc];
}
@end
説明:
ビューは、モデルオブジェクト(プレーヤ)からの通知を適切に加入します。それは通知をキャプチャする場合、それはすでに(animatingPlayerプロパティを使用して)アニメーションをやっているかどうかを確認します。もしそうなら、それは(プレイヤーがアニメ化されることになっているかに注目)通知からの情報を受け取り、辞書にその情報を入れて、アニメーションキューにその辞書を追加します。現在起こって何のアニメーションが存在しない場合、この方法は、[どのような]アニメルーチンtrueにanimatingPlayerを設定し、適切なDOを呼び出します。
終了:各行う[どのような]アニメルーチンはanimationDidStopにsetAnimationDidStopSelectorを設定し、適切なアニメーションを実行コンテキストを:.各アニメーションが終了すると、animationDidStop:終了:コンテキスト:(すべてのアニメーションを直ちに停止すべきかどうかを確認した後)メソッドがコールするために、そのデータをキューから次の辞書を引いて解釈することにより、キュー内の次のアニメーションを実行します適切なDO [どのような]アニメ方法。プレイヤーは適切にアニメーションの現在の実行を停止した際にNOとポストへのルーチンセットanimatingPlayerは、通知がそう他のオブジェクトが知っていることをキューにアニメーションは、存在しない場合ます。
それはそれについてです。そこ(?)単純な方法であるが、これは私のためにかなりよく働いたことがあります。あなたが実際の結果を見ることに興味があるならApp Storeで私MAZINアプリをチェックします。
感謝します。
他のヒント
私はあなたがanimationDidStopを受信するように連続して起こるとアニメーションにデリゲートを設定する必要がアニメーションのキューを設定され、ここでやりたいかもしれないものだと思います。この方法で1つのアニメーションが完了したときにキューの次の1をオフに設定することができます。
あなたは以下に示すように、アレイ内のアニメーションパスに沿って複数のポイントを提供する考えなければなりません。
以下の例では、y軸に沿って複数のポイントを指定しますが、あなたはまた、あなたのアニメーションをフォローしたいベジェパスを指定することができます。
基本的なアニメーションとキーフレームアニメーションとの主な違いは、キーフレームを使用して、パスに沿って複数のポイントを指定することができることです。
CAKeyframeAnimation *downMoveAnimation;
downMoveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
downMoveAnimation.duration = 12;
downMoveAnimation.repeatCount = 1;
downMoveAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:20],
[NSNumber numberWithFloat:220],
[NSNumber numberWithFloat:290], nil];
downMoveAnimation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0], nil];
downMoveAnimation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
// from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil];
// from keyframe 2 to keyframe 3
downMoveAnimation.removedOnCompletion = NO;
downMoveAnimation.fillMode = kCAFillModeForwards;