2009-12-08 10 views
0

특정 속성이 변경 될 때 모델에 알림이 전송됩니다. 결과적으로, 특정 뷰 객체의 선택자는 통지를 포착하여 그에 따라 뷰의 위치를 ​​변경합니다.CABasicAnimation을 사용하여 연속 애니메이션 강제 적용

알림을 사용하면 창의보기가 특정 방향 (항상 세로 또는 가로 및 항상 창에서 표준 단계 크기로 이동)으로 이동합니다. 사용자 작업으로 여러 알림을 차례로 실행할 수 있습니다. 예를 들어 세 가지 알림을 전송하여보기를 세 단계 아래로 이동 한 다음 두 번 더 알림을 보내보기를 오른쪽 두 단계로 이동할 수 있습니다.

문제는 내가 애니메이션을 실행할 때 연속적으로 발생하지 않는다는 것입니다. 앞의 예제에서, 뷰를 3 칸 아래로 천천히 이동시킨 다음 알림의 결과로 두 칸을 이동하려고하지만, 대신에 새로운 위치로 대각선으로 이동합니다. 여기

내 두 선택기에 대한 코드입니다 (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"]; 
} 

이 방법의 힘을 여러 번 호출을 만드는 방법에 대한 어떤 제안 한번에 단계별로 실행하는 애니메이션 감사!!

답변

1

구현 한 솔루션은 실제로 대기열을 사용합니다. 여기에 아주 완벽한 설명이 있습니다 :

이것은 모두 PlayerView라는보기 클래스에서 수행됩니다. 곁에있는 NSMutableArray + QueueAdditions.h/m에서 QueueAdditions 카테고리 이렇게 보이는 바와

#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 

: 헤더 I는 다음과 같다

@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 속성을 사용하여 애니메이션을 수행하고 있는지 확인합니다. 그렇다면 알림에서 정보를 가져 와서 (플레이어가 어떻게 움직일 것으로 예상되는지), 그 정보를 사전에 넣고 사전을 애니메이션 대기열에 추가합니다. 현재 진행중인 애니메이션이 없으면 메서드는 animatingPlayer를 true로 설정하고 적절한 [Whatever] 애니메이션 루틴을 호출합니다.

각 [Any] Animation 루틴은 적절한 애니메이션을 수행하고 setAnimationDidStopSelector를 animationDidStop : finished : context :로 설정합니다. 각 애니메이션이 완료되면 animationDidStop : finished : context : 메서드 (모든 애니메이션을 즉시 중지해야하는지 여부를 확인한 후)는 다음 사전을 대기열에서 꺼내어 데이터를 해석하여 해당 애니메이션을 호출하여 다음 애니메이션을 수행합니다. 적절한 [뭐든지] 애니메이션 방법.대기열에 애니메이션이 없으면 해당 루틴은 animatingPlayer를 NO로 설정하고 다른 객체가 플레이어가 현재 애니메이션 실행을 적절하게 중지 한 시점을 알 수 있도록 알림을 게시합니다.

그게 전부입니다. 더 간단한 방법 (?)이 있을지도 모르지만 이것은 나를 위해 꽤 잘 작동했습니다. 실제 결과를보고 싶다면 App Store의 Mazin 앱을 확인하십시오.

감사합니다.

2

여기에서 원하는 것은 연속적으로 발생해야하는 애니메이션 큐를 설정하고 애니메이션 위임자를 설정하여 animationDidStop : finished : 메시지를 수신하도록하는 것입니다. 이 방법으로 하나의 애니메이션이 완료되면 큐에서 다음 애니메이션을 설정할 수 있습니다.

0

아래와 같이 배열에서 애니메이션 경로를 따라 여러 지점을 제공하는 것이 좋습니다.

아래 예제는 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; 
관련 문제