سؤال

لدي إشعار ينطلق في نموذجي عند تغيير خصائص معينة. نتيجة لذلك، يمسك أحد محدد كائن عرض معين بإخطارات تغيير موضع العرض وفقا لذلك.

تسبب الإخطارات في عرض على النافذة للتحرك في اتجاه معين (دائما رأسيا أو أفقيا ودائما من خلال حجم خطوة قياسي على النافذة). من الممكن أن تسبب إجراءات المستخدم - الإخطارات عدة إشعارات واحدة تلو الأخرى. على سبيل المثال، يمكن إرسال 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"];
}

أي اقتراحات حول كيفية إجراء مكالمات متعددة إلى هذه الطرق تجبر الرسوم المتحركة لتنفيذ خطوة بخطوة بدلا من كل مرة؟ شكرا!!

هل كانت مفيدة؟

المحلول 2

الحل الذي قمت بتنفيذه يستخدم بالفعل قائمة انتظار. إليك وصف كامل للغاية:

يتم ذلك كل ذلك في فئة عرض يسمى Playerview. في الرأس، أدرج ما يلي:

#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

كقدم، فئة QueuEditions في NSMutablerray + Queueadditions.h / M تبدو وكأنها هذه:

@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 وتدعو روتين رسوم متحركة من المناسب.

تقوم كل منها [أيا كان] روتين الرسوم المتحركة إن الرسوم المتحركة المناسبة، وإعداد SetanimationDIDStopSelector إلى AnimationDIDSTTOP: انتهى: السياق :. عندما تنتهي كل الرسوم المتحركة، فإن AnimationDIDSTOT: انتهى: السياق: الطريقة (بعد التحقق من ذلك، يجب إيقاف جميع الرسوم المتحركة على الفور) إجراء الرسوم المتحركة التالية في قائمة الانتظار من خلال سحب القاموس التالي من قائمة الانتظار وتفسر بياناتها من أجل الاتصال ببياناتها مناسبة تفعل [أيا كان] طريقة الرسوم المتحركة. إذا لم تكن هناك رسوم متحركة في قائمة الانتظار، فإن هذا روتين يقوم بتعيين الرسوم المتحركة إلى NO ويؤدي إلى إشعار حتى يمكن أن تعرف الكائنات الأخرى عند إيقاف اللاعب بشكل مناسب عن تشغيل الرسوم المتحركة الحالية.

هذا عن ذلك. قد تكون هناك طريقة أبسط (؟) ولكن هذا عملت جيدا بالنسبة لي. تحقق من تطبيق My Mazin في متجر التطبيقات إذا كنت مهتما برؤية النتائج الفعلية.

شكرا.

نصائح أخرى

أعتقد أن ما قد ترغب في القيام به هنا يقوم بإعداد قائمة انتظار الرسوم المتحركة التي تحتاج إلى حدوثها على التوالي وتعيين مفوض الرسوم المتحركة بحيث ستتلقى الرسوم المتحركة: الانتهاء: رسالة. بهذه الطريقة عند اكتمال رسوم متحركة واحدة، يمكنك إيقاف تشغيلها التالية في قائمة الانتظار.

يجب أن تفكر في توفير نقاط متعددة على طول مسار الرسوم المتحركة في صفيف كما هو موضح أدناه.

يحدد المثال أدناه نقاطا متعددة على طول المحور Y ولكن يمكنك أيضا تحديد مسار Bzier الذي تريد متابعة الرسوم المتحركة الخاصة بك.

الفرق الرئيسي بين الرسوم المتحركة الأساسية والرسوم المتحركة الإطار الرئيسي هو أن الإطار الرئيسي يسمح لك بتحديد نقاط متعددة على طول المسار.

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;
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top