2009-12-08 5 views
0

J'ai une notification qui se déclenche dans mon modèle lorsque certaines propriétés changent. Par conséquent, un sélecteur dans un objet de vue particulier intercepte les notifications pour modifier la position de la vue en conséquence. Les notifications provoquent le déplacement d'une vue sur la fenêtre dans une direction particulière (toujours verticalement ou horizontalement et toujours par une taille de pas standard sur la fenêtre). Il est possible que les actions utilisateur déclenchent plusieurs notifications les unes après les autres. Par exemple, 3 notifications peuvent être envoyées pour déplacer la vue en trois étapes, puis deux autres notifications peuvent être envoyées pour déplacer la vue vers les deux étapes de droite.Forçage d'animations consécutives avec CABasicAnimation

Le problème est que lorsque j'exécute les animations, elles ne se produisent pas de manière consécutive. Ainsi, dans l'exemple précédent, bien que je souhaite que la vue se déplace lentement vers le bas de trois espaces et se déplace ensuite sur deux espaces à la suite des notifications, elle finit par se déplacer en diagonale vers la nouvelle position.

Voici le code pour mes deux sélecteurs (notez que placePlayer définit la position de la vue selon les informations actuelles dans le modèle):

- (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"]; 
} 

Toutes les suggestions sur la façon de faire des appels multiples à cette force de méthodes animation à exécuter étape par étape plutôt que tout à la fois? Merci!!

Répondre

1

La solution que j'ai implémentée utilise en effet une file d'attente. Voici une description assez complète:

Tout cela est fait dans une classe de vue appelée PlayerView. Dans l'en-tête I comprennent les suivants:

#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 

En aparté, la catégorie QueueAdditions dans NSMutableArray + QueueAdditions.h/m ressemble à ceci:

@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 

Ensuite, dans la mise en œuvre du PlayerView, Je donne les résultats suivants:

#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 

Explication:

la vue sous scribes aux notifications appropriées de l'objet modèle (lecteur). Quand il capture une notification, il vérifie s'il fait déjà une animation (en utilisant la propriété animatingPlayer). Si c'est le cas, il prend les informations de la notification (en notant comment le joueur est censé être animé), place ces informations dans un dictionnaire et ajoute ce dictionnaire à la file d'attente d'animation. Si aucune animation n'est en cours, la méthode placera animatingPlayer sur true et appellera une routine d'animation appropriée [Whatever]. Chaque routine d'animation [Whatever] effectue les animations appropriées, en définissant setAnimationDidStopSelector sur animationDidStop: finished: context :. À la fin de chaque animation, la méthode animationDidStop: finished: context: (après avoir vérifié si toutes les animations doivent être arrêtées immédiatement) exécutera l'animation suivante dans la file en extrayant le dictionnaire suivant de la file d'attente et en interprétant ses données approprié faire [Whatever] Méthode d'animation.S'il n'y a pas d'animations dans la file d'attente, cette routine définit animatingPlayer sur NO et envoie une notification afin que les autres objets puissent savoir quand le joueur a correctement arrêté sa série d'animations en cours.

C'est à peu près tout. Il peut y avoir une méthode plus simple (?) Mais cela a plutôt bien fonctionné pour moi. Consultez mon application Mazin dans l'App Store si vous êtes intéressé à voir les résultats réels.

Merci.

2

Je pense que ce que vous pourriez vouloir faire ici est de mettre en place une file d'animations qui doivent se produire de manière consécutive et de définir les animations de sorte que vous receviez le message animationDidStop: finished:. De cette façon, lorsqu'une animation est terminée, vous pouvez activer la suivante dans la file d'attente.

0

Vous devez penser à fournir plusieurs points le long du chemin d'animation dans un tableau, comme illustré ci-dessous.

L'exemple ci-dessous spécifie plusieurs points le long de l'axe des ordonnées, mais vous pouvez également spécifier un chemin de Bézier que vous voulez que votre animation suive. La principale différence entre l'animation de base et l'animation par image clé est que Key-frame vous permet de spécifier plusieurs points le long du chemin.

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; 
Questions connexes