如何使用 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 控制动画序列的主要内容,如果未能解决你的问题,请参考以下文章

如何将层文件序列转换为 .fbx 动画

如何使用动画进行刷新控制?

如何使用 papervision3D 控制 collada 模型的动画?

CSS如何实现动画?

如何使用核心动画创建导航控制器推送效果?

unity怎么让贴图序列播放