关于UIView animation,很多读者相信非常喜欢用animation block的动画API,如下:
Animating Views with Blocks + animateWithDuration:delay:options:animations:completion: + animateWithDuration:animations:completion: + animateWithDuration:animations: + transitionWithView:duration:options:animations:completion: + transitionFromView:toView:duration:options:completion:
当然笔者也一样,但是关于block还是有一些需要注意的地方的,比如:关于动画的意外停止,在动画的时候,突然按Home键退出应用。笔者前几日碰到一个关于类似的UIViewAnimation方面的bug,在这里记录一下。请看如下的代码:
- (void)animationMoveViewRightOut:(UIView *)view completion:(void (^)(BOOL finished))completion { [UIView animateWithDuration:1.0f animations:^{ CGRect bounds = [UIScreen mainScreen].bounds; view.center = CGPointMake ( bounds.size.width + view.frame.size.width/2, view.center.y); }completion:^(BOOL finished ) { if (completion) { completion(finished); } }]; } - (void)animationMoveViewToCenter:(UIView *)view { [UIView animateWithDuration:1.0f delay:0.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{ CGRect bounds = [UIScreen mainScreen].bounds; view.center = CGPointMake (bounds.size.width / 2, bounds.size.height / 2 ); }completion:^ (BOOL finished){ if (finished) { [self animationMoveViewRightOut:view completion:^(BOOL finished) { if (finished) { NSLog(@"animationMoveViewRightOut!!!"); } }]; } }]; }
这段动画代码就是先将某个view 动画移动到屏幕中央,移动到中央的动画结束的时候接着将该视图移动出右边的屏幕之外。你能看出这段动画有啥问题么?这段代码平时运行的挺好的,完全没啥问题,但是当执行移动视图到屏幕中间期间,按下Home 键,应用进入后台,再进入前台的时候,你可能会发现,视图没有移动出屏幕之外,动画被冻结了。为什么呢?笔者发现,当屏幕进入后台的时候,UIview 的 block 动画会瞬间完成,并执行completion 块动画,这时传递过去的finished 的 bool 值是false , completion 里关于移 动视图到屏幕外边的动画就不会执行了。
发现这个问题之后,笔者将所有动画结束的 if ( finished )判断 语句去掉。这时在移动视图到屏幕中央期间再按下Home 键,让应用进入后台,再进入前台,这时视图已经看不见了,从打印的信息能看到 “animationMoveViewRightOut !!! ” 的 语句,这表明视图移动到右边屏幕外的动画已经瞬间执行了。好,关于 UIView 的 block 动画注意点总结: 1 、 动画期间,当按下 Home 键时,动画将会瞬间完成; 2. 动画虽然是瞬间完成了,但是 completion 的返回的 finished 的 bool 值是 false 。
为解决这个问题,如果你不需要在从后台进入前台继续执行动画,你可以将所有的 finish 判断 语句去掉,这个让动画瞬间完成,防止导致一些意外情况;另外你也可以在进入后台的时候用 [aView.layerremoveAllAnimations]; 语句来将该视图的所有动画 remove 掉, 动画也会瞬间完成。在此多解释几句: [aView.layer removeAllAnimations]; 不 仅可以 remove 掉 CABasicAnimation 等 CoreAnimation 的 动画,也可以 remove 掉所有的 UIView animation 就是类似文章开 头的 API 的 动画以及 UIImageView 的 animationImages 来 startAnimating 的序列 帧动画,由此可见, UIImageView 的序列 帧动画以及 UIView animation 的插 值动画的底层实现都是用 CoreAnimation 来 实现的。 见如下代码实例:
aView = [[UIImageView alloc] initWithFrame:CGRectMake(0, viewController.view.frame.size.height /2 - 50, 100, 100)]; aView.backgroundColor = [UIColor redColor]; aView.animationDuration = 10 ; aView.animationImages = [NSArray arrayWithObjects: [UIImage imageNamed: @"CloseNormal.png"],[UIImage imageNamed: @"Icon.png"],[UIImage imageNamed: @"CloseSelected.png"],[UIImage imageNamed:@"Icon-Small-50.png" ], nil]; [aView startAnimating]; [viewController.view addSubview:aView]; [aView release]; [self animationMoveViewToCenter:aView]; [self performSelector:@selector(removeAnimation:) withObject:nil afterDelay:2]; - (void)removeAnimation:(id)sender { // [aView stopAnimating]; [aView.layer removeAllAnimations]; }
所以,如果你想要能控制更多的 动画内容,比如你想能够在进入后台的时候让动画停止,进入前台的时候又让动画恢复运行,你可以尝试用CoreAnimation 来 实现动画。代码实例如下:
- (void)rotationAnimation:(UIView *)view { CABasicAnimation *rotation; rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotation.toValue = [NSNumber numberWithFloat: M_PI * 2]; rotation.duration = 5 ; rotation.cumulative = YES ; rotation.repeatCount = 4 ; rotation.removedOnCompletion = NO ; rotation.fillMode = kCAFillModeForwards ; rotation.delegate = self ; [view.layer addAnimation:rotation forKey:@"rotationAnimation"]; } - (void)animationDidStart:(CAAnimation *)theAnimation { NSLog(@"animationDidStar"); } - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { NSLog(@"animationDidStop finished %d",flag); } [self rotationAnimation:aView];[self performSelector:@selector(removeAnimation:) withObject:nil afterDelay:2]; //注意,第一次pauseLayer的时候,就会触发 - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag 函数,以后将不会再次触发,或者[aView.layer removeAllAnimations];也能触发;总之,- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag只会被触发一次,触发一次之后,再次发生能触发该函数的情况将不再调用该函数。 -(void)pauseLayer:(CALayer*)layer //暂停动画 { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0 .0; layer.timeOffset = pausedTime ; } -(void)resumeLayer:(CALayer*)layer //恢复动画 { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1 .0; layer.timeOffset = 0 .0; layer.beginTime = 0 .0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause ; } - (void)applicationDidBecomeActive:(UIApplication *)application { /* Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. */ [self resumeLayer:aView.layer]; } - (void)applicationDidEnterBackground:(UIApplication *)application { /* Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. If your application supports background execution, called instead of applicationWillTerminate: when the user quits. */ [self pauseLayer:aView.layer]; }
好了,再次总结一下,调用以上 -(void)pauseLayer:(CALayer*)layer函数,可以 暂停一个 layer tree的所有 animation;而 调用上面提供的 -(void)resumeLayer:(CALayer*)layer函数可以恢复一个 layer tree的所有 animation。本篇到此 结束。