如何使用 GCD 控制动画序列

Posted

技术标签:

【中文标题】如何使用 GCD 控制动画序列【英文标题】:How to use GCD to control animation sequence 【发布时间】:2014-12-22 11:25:18 【问题描述】:

我有一个View 显示和隐藏给用户一些提示。

显示和隐藏方法如下所示:

-(void)show
    [UIView animateWithDuration:3.0f
                 animations:^
                     //do something to show self to give hint;
                     self.frame = CGRectMake(0,0,0,0);
                  completion:nil];


-(void)hide
    [UIView animateWithDuration:3.0f
                 animations:^
                     //do something to hide self to give hint;
                     self.frame = CGRectMake(centerX,centerY,100,100);
                  completion:nil];

显示新视图时,我必须调用hide method,然后调用show method。但是持续时间延迟 3.0f 会导致一些错误。我正在使用这样的方法:

dispatch_async(dispatch_get_main_queue(), ^
    [view hide];
);

dispatch_async(dispatch_get_main_queue(), ^
    [view show];
);

我在hide method 之后打电话给show method。动画不能按照它们提交到队列的顺序执行。我想要的是在hide method 完成之后执行的show method。如何控制这两种方法的顺序。

我认为我无法使用完成处理程序,因为我无法确定调用这两个方法的位置,或者当我调用另一个 show methodhide method 时是否显示视图。

如果我不清楚,有什么建议吗?我将重新编辑我的问题。


PS:

这不仅仅是一个闪光。当调用下一个 show 方法时,我无法保证显示或隐藏最后一个视图以及显示最后一个视图的时间,也就是说,如果正在显示视图并且已经调用了 hide 方法并且已经完成,然后调用show方法,结果是对的。如果正在显示视图,则需要显示另一个提示视图,我会先调用hide,然后调用show,因为main_queue 是串行的但动画块是同步执行的,所以结果是错误的。我正在寻找 GCD 中是否有某种锁可以帮助我在最后一个排队块完成后执行一个块,而不是在 showhide 方法内更改。因为有许多其他对 showhide 方法的调用具有许多不同类型的参数,我需要修复我的代码中的许多地方。

【问题讨论】:

An answer to this question 建议使用关键帧动画,这可能对您有用。 将标志传递给 show 或 hide 方法,指示您是否要调用下一个。然后在完成方法中对其进行操作。 @jrturton 的解决方案将起作用。您还可以创建一个NSOperationQueue 并将您的显示/隐藏命令添加到此队列。如果操作不再运行,您可以调用 cancel @AaronBrager 我试过 NSOperationQueue。似乎即使我在将新操作添加到队列之前取消了所有操作,添加到队列的动画也无法取消。 【参考方案1】:

如果您想按照添加到队列中的顺序一次执行一项任务,请使用串行队列。

因此您可以使用串行队列按添加的顺序一次执行显示和隐藏任务。是的,主队列没问题。

然而 UIView -animateWithDuration:animations: 方法是一种异步调用,该方法立即返回。所以你需要等到完成块被调用。

如果您想等到某些任务完成,请使用调度组。但是你应该避免在主队列上等待。它阻塞了主队列。糟糕的应用程序。

因此,您可能需要使用串行队列和调度组,如下所示。

属性和初始化

@property (nonatomic, strong) dispatch_queue_t serialQueue;
@property (nonatomic, strong) dispatch_group_t group;

-(void)initQueue 
    // create a serial queue
    self.serialQueue = dispatch_queue_create("com.example.serialQueue", 0);
    // create a dispatch group
    self.group = dispatch_group_create();

使用串行队列和调度组的方法

-(void)animateSyncWithDuration:(NSTimeInterval)duration animations:(block_t)animations 
    dispatch_async(self.serialQueue, ^
        /*
         * This block is invoked on the serial queue
         * This block would never be executed concurrently
         */

        /*
         * Enter the dispatch group
         */
        dispatch_group_enter(self.group);

        dispatch_async(dispatch_get_main_queue(), ^
            /*
             * This block is invoked on the main queue
             * It is safe to use UIKit
             */
            [UIView animateWithDuration:duration animations:animations completion:^
                /*
                 * This completion block is invoked on the main queue
                 * Now leave the dispatch group
                 */
                dispatch_group_leave(self.group);
            ];
        );

        /*
         * Wait until leaving the dispatch group from the UIView animation completion block
         * It means it blocks the serial queue
         */
        dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER);
    );

显示和隐藏

-(void)show
    [self animateSyncWithDuration:3.0f animations:^
        //do something to show self to give hint;
        self.frame = CGRectMake(0,0,0,0);
    ];


-(void)hide
    [self animateSyncWithDuration:3.0f animations:^
        //do something to hide self to give hint;
        self.frame = CGRectMake(centerX,centerY,100,100);
    ];

【讨论】:

【参考方案2】:

如果您想要的是一个动作(隐藏然后显示自己),您应该只制作一个动画来执行此操作,而不是合并两个动画。

有两种可能的解决方案。

(1) 使用动画重复和自动反转(需要在完成回调中重置回原始大小)

-(void) flash 
  CGRect bounds = self.bounds;

  [UIView animateWithDuration:1.0f
                        delay:0.0f
                      options:UIViewAnimationOptionAutoreverse |
                              UIViewAnimationOptionRepeat
                   animations:^
                     [UIView setAnimationRepeatCount:1];
                     self.bounds = CGRectZero;
                   
                   completion:^(BOOL finished) 
                     self.bounds = bounds;
                   ];

(2)使用关键帧动画

-(void) flash2 
    [UIView animateKeyframesWithDuration:1.0f
        delay:0.0f
      options:UIViewKeyframeAnimationOptionCalculationModeLinear
   animations:^
       CGRect bounds = self.bounds;

       [UIView addKeyframeWithRelativeStartTime:0.0
                               relativeDuration:0.5
                                     animations:^ self.bounds = CGRectZero; ];

       [UIView addKeyframeWithRelativeStartTime:0.5
                               relativeDuration:0.5
                                     animations:^ self.bounds = bounds; ];
    
   completion:nil];

【讨论】:

【参考方案3】:

我正在使用以下方式将delay 放入function calling

- (void) doAnimation : (double) delay 

    dispatch_async(dispatch_get_main_queue(), ^
        NSLog(@"Call your First function here");
    );

    double delayInSeconds = delay;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void)

        NSLog(@"Call your second function here");

    );
 

【讨论】:

延迟秒数可能会改变,有什么不使用延迟秒数的建议吗? 创建以delay seconds为参数的函数。或者你可以在Animation 中使用completion block。见updated answer【参考方案4】:

我可能没有完全理解用例,但我认为你应该在这里做的是检查隐藏操作是否真的需要发生。此外,由于代码中隐藏的动画持续时间为 3 秒,因此您应该使用完成块创建您的方法,这样您就可以执行类似于我在下面用伪代码编写的内容:

- (void)hideIfNeededWithCompletionBlock:((^)())completionBlock 
    if (self.isShowing) 
        [self hideWithCompletionBlock:^(BOOL didHide) 
            if (completionBlock) 
                completionBlock();
            
        ];
     else 
        if (completionBlock) 
            //We didn't need to hide anything, so we're done
            completionBlock();
        
    

那么你可以这样称呼它:

[self hideIfNeededWithCompletionBlock:^()
    [self show];
];

如果您需要这种灵活性,您可以使用 show 方法执行类似的操作。

此外,根据您的需要,您可以让您的方法采用 BOOL 来确定是否为显示/隐藏设置动画,如果您通过 NO,则使用 0.0 的持续时间。

我认为当您可以使用提供的 API 处理所有内容时,它会针对 UIView 动画 API 开始将其包装在 dispatch_async 块中。

【讨论】:

我知道。即使我不使用GCD,只是直接将show和hide一起使用,结果也是错误的,除了我在完成块内调用show或hide。 我正在寻找更好的解决方案,比如某种锁,以确保如果 show 方法没有完成,则 hide 方法将等到 show 方法完成。 因此可以使用 BOOL 来跟踪是否存在待处理的隐藏操作,并使用 BOOL 来跟踪是否正在发生显示动画。如果您在显示动画中,请将您的 pendingHide BOOL 设置为 YES。显示后,如果有未决隐藏,则执行它(从动画完成块调用)。

以上是关于如何使用 GCD 控制动画序列的主要内容,如果未能解决你的问题,请参考以下文章