如何使用 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 method
或 hide method
时是否显示视图。
如果我不清楚,有什么建议吗?我将重新编辑我的问题。
PS:
这不仅仅是一个闪光。当调用下一个 show
方法时,我无法保证显示或隐藏最后一个视图以及显示最后一个视图的时间,也就是说,如果正在显示视图并且已经调用了 hide
方法并且已经完成,然后调用show
方法,结果是对的。如果正在显示视图,则需要显示另一个提示视图,我会先调用hide
,然后调用show
,因为main_queue
是串行的但动画块是同步执行的,所以结果是错误的。我正在寻找 GCD 中是否有某种锁可以帮助我在最后一个排队块完成后执行一个块,而不是在 show
和 hide
方法内更改。因为有许多其他对 show
和 hide
方法的调用具有许多不同类型的参数,我需要修复我的代码中的许多地方。
【问题讨论】:
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 控制动画序列的主要内容,如果未能解决你的问题,请参考以下文章