[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{ //第一帧要执行的动画 }]; [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{ //第二帧要执行的动画 }]; } completion:^(BOOL finished) { //动画结束后执行的代码块 }];新引入的animateKeyframesWithDuration与CAKeyframeAnimation的关系,可以比对animateWithDuration和CABasicAnimation,我们只需要将每一帧动画加入到block方法中,并传入此段动画在全过程中的相对开始时间和执行时间(duration具体是指此段动画的执行时间占全过程的百分比)。同时,你可以在一次动画中使用多个关键帧,只需使用addKeyframe依次将所有关键帧加入动画执行栈中。
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.15 animations:^{ //顺时针旋转90度 snapshot.transform = CGAffineTransformMakeRotation(M_PI * -1.5); }]; [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.10 animations:^{ //180度 snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.0); }]; [UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.20 animations:^{ //摆过中点,225度 snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.3); }]; [UIView addKeyframeWithRelativeStartTime:0.45 relativeDuration:0.20 animations:^{ //再摆回来,140度 snapshot.transform = CGAffineTransformMakeRotation(M_PI * 0.8); }]; [UIView addKeyframeWithRelativeStartTime:0.65 relativeDuration:0.35 animations:^{ //旋转后掉落 //最后一步,视图淡出并消失 CGAffineTransform shift = CGAffineTransformMakeTranslation(180.0, 0.0); CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI * 0.3); snapshot.transform = CGAffineTransformConcat(shift, rotate); _coverView.alpha = 0.0; }];视图仿佛在重力的牵引下绕左下角顺时针旋转,并在最低点摆动了一下,最后脱落。
[UIView animateWithDuration:duration delay:delay usingSpringWithDamping:damping initialSpringVelocity:velocity options:options animations:^{ //这里书写动画相关代码 } completion:^(BOOL finished) { //动画结束后执行的代码块 }];这里用到了一些物理上的概念:damping参数代表弹性阻尼,随着阻尼值越来越接近0.0,动画的弹性效果会越来越明显,而如果设置阻尼值为1.0,则视图动画不会有弹性效果——视图滑动时会直接减速到0并立刻停止,不会有弹簧类的拉伸效果。
-(void)animateTransition: (id)transitionContext { //获取容器视图引用 UIView *containerView = [transitionContext containerView]; UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey ]; UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; if (self.type == AnimationTypePresent) { //插入“to”视图,初始缩放值为0.0 toViewController.view.transform = CGAffineTransformMakeScale(0.0, 0.0); [containerView insertSubview:toViewController.view aboveSubview:fromViewController.view]; //缩放“to”视图为想要的效果 [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ toViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0); } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }]; } else if (self.type == AnimationTypeDismiss) { //插入“to”视图 [containerView insertSubview:toViewController.view belowSubview:fromViewController.view]; //缩小“from”视图,直到其消失 [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.transform = CGAffineTransformMakeScale(0.0, 0.0); } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }]; } } -(NSTimeInterval)transitionDuration: (id)transitionContext { return 0.4; }符合UIViewControllerAnimatedTransitioning协议的任何对象都需要实现animateTransition:和transitionDuration:两个方法。你也可以选择实现@optional方法animationEnded:,它在动画完成后由系统自动调用,相当于completion block,非常方便。
显然,苹果公司帮助开发者完成了大部分让人讨厌的细节工作,仅仅需要我们自己完成的工作就是定义动画的初始状态和终止状态,并调整到自己满意的效果。最后我再啰嗦两句有关transitionContext的重要注意事项:
1.获取frame的方法可能会返回CGRectZero——如果系统无法确定该frame的值具体是什么。例如,如果你使用自定义的模态视图控制器
推出动画,在结束时系统无法确定其finalFrame。
2.如果视图控制器已经从屏幕上移除了,那么获取frame的方法也会返回CGRectZero。例如在导航控制器的转场动画结束后,试图获取“from”视图的finalFrame。
你不用手动去移除“from”视图,transitionContext将自动帮你完成。
3.如果你在应用的其他地方需要使用transitionContext,你可以放心地使用动画控制器保留一个transitionContext的引用。
将动画控制器应用到转场动画中。
现在,我们已经开发好了动画控制器,那么最后需要做的就是,将它们应用到转场动画中:我们需要对管理转场动画的UIViewController做一些操作。
一般来说,我们只需要让UIViewController符合UIViewController-TransitioningDelegate 协议, 编写animationController-ForPresentedController和animationControllerForDismissedController方法。在我的示例应用程序中,我设置了一个属性,用来让动画控制器知道目前正在推入还是推出视图:
-(id) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { modalAnimationController.type = AnimationTypePresent; return modalAnimationController; } -(id) animationControllerForDismissedController:(UIViewController *)dismissed { modalAnimationController.type = AnimationTypeDismiss; return modalAnimationController; }然后,在推入模态视图控制器时,我们设置modalPresentationStyle为UIModalPresentationFullScreen或UIModalPresentationCustom。我们还必须将一个符合UIViewControllerTransitioningDelegate协议的对象设置为它的transitioningDelegate,一般来说都是推入该模态视图控制器的UIViewController。
OptionsViewController *modal = [[OptionsViewController alloc] initWithNibName:@"OptionsViewController" bundle:[NSBundle mainBundle]]; modal.transitioningDelegate = self; modal.modalPresentationStyle = UIModalPresentationCustom; [self presentViewController:modal animated:YES completion:nil];如果需要将动画控制器应用到UINavigationController的转场动画中,我们需要使用UINavigationControllerDelegate协议中的一个新方法:animationControllerForOperation。对于任何自定义的导航转场动画,导航栏都会有一个淡入淡出的动画过程。同样,对于UITabBarController,使用UITabBarControllerDelegate协议的新方法——animationController-ForTransitionFromViewController。
@interface UIPercentDrivenInteractiveTransition : NSObject@property (readonly) CGFloat duration; @property (readonly) CGFloat percentComplete; @property (nonatomic,assign) CGFloat completionSpeed; @property (nonatomic,assign) UIViewAnimationCurve completionCurve; - (void)updateInteractiveTransition:(CGFloat)percentComplete; - (void)cancelInteractiveTransition; - (void)finishInteractiveTransition;这个类具体实现了UIViewControllerInteractiveTransitioning协议,我们可以使用它轻松为动画控制器添加自定义的交互方式。只要为目标视图加入手势(或者其他交互方式)并调用updateInteractiveTransition:,传入动画时间占整个过程的百分比即可。同时, 记住在交互完成后调用finishInteractiveTransition: , 交互被取消时调用cancel-InteractiveTransition:。下面的例子展示了如何将捏合手势应用到转场动画中:
-(void)handlePinch:(UIPinchGestureRecognizer *)pinch { CGFloat scale = pinch.scale; switch (pinch.state) { case UIGestureRecognizerStateBegan: { _startScale = scale; self.interactive = YES; [self.navigationController popViewControllerAnimated:YES]; break; } case UIGestureRecognizerStateChanged: { CGFloat percent = (1.0 - scale/_startScale); [self updateInteractiveTransition:(percent < 0.0) ? 0.0 : percent]; break; } case UIGestureRecognizerStateEnded: { CGFloat percent = (1.0 - scale/_startScale); BOOL cancelled = ([pinch velocity] < 5.0 && percent <= 0.3); if (cancelled) [self cancelInteractiveTransition]; else [self finishInteractiveTransition]; break; } case UIGestureRecognizerStateCancelled: { CGFloat percent = (1.0 - scale/_startScale); BOOL cancelled = ([pinch velocity] < 5.0 && percent <= 0.3); if (cancelled) [self cancelInteractiveTransition]; else [self finishInteractiveTransition]; break; } } }当你继承了UIPercentDrivenInteractiveTransition类,交互过程中系统会自动调用动画控制器的animateTransition:方法,按照你传递的percentComplete参数实时地展现动画效果。在交互完成后,它还自动调用animateTransition:方法恢复到正常状态,一旦交互完成,我们就可以改变completionSpeed和completionCurve属性来修改其他的一些样式。
-(void)startInteractiveTransition: (id)transitionContext { //获取transitionContext对象的引用 _context = transitionContext; //获取容器视图引用 UIView *containerView = [transitionContext containerView]; UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey ]; UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //插入“to”视图 toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; [containerView insertSubview:toViewController.view belowSubview:fromViewController.view]; //保留需要缩?小的视图的引用 _transitioningView = fromViewController.view; } -(void)updateWithPercent:(CGFloat)percent { CGFloat scale = fabsf(percent-1.0); _transitioningView.transform = CGAffineTransformMakeScale(scale, scale); [_context updateInteractiveTransition:percent]; } -(void)end:(BOOL)cancelled { if (cancelled) { [UIView animateWithDuration:_completionSpeed animations:^{ _transitioningView.transform = CGAffineTransformMakeScale(1.0, 1.0); } completion:^(BOOL finished) { [_context cancelInteractiveTransition]; [_context completeTransition:NO]; }]; } else { [UIView animateWithDuration:_completionSpeed animations:^{ _transitioningView.transform = CGAffineTransformMakeScale(0.0, 0.0); } completion:^(BOOL finished) { [_context finishInteractiveTransition]; [_context completeTransition:YES]; }]; } }你可以让动画控制器同时实现UIViewControllerInteractive-Transitioning和 UIViewControllerAnimatedTransitioning(像示例程序中那样),从而把所有代码都放在一个类中。你也可以将交互控制器和动画控制器分成两个类——协议这一语法特性的妙处在于,你可以轻松实现符合需求的最佳解决方案。
[UIView performWithoutAnimation:^{ //确保不执行动画 }];你可以随时执行这段代码来控制不需要执行的动画。
CollectionViewController *VC = [[CollectionViewController alloc] initWithCollectionViewLayout:flowLayout]; VC.title = @"Mini Apples"; VC.useLayoutToLayoutNavigationTransitions = YES; [self.navigationController pushViewController:VC animated:YES];转场动画调度器
[self.transitionCoordinator animateAlongsideTransition:^(idrdinatorContext> context) { //要执行的动画 } completion:^(id context) { //动画结束后执行的代码块 }];我们可以通过这个方法在进行转场动画时并行执行一些其他动画,context参数和之前提到的符合UIViewControllerContextTransitioning协议的transitionContext参数相类似,从该参数中我们可以获取有关转场过程的一些重要信息,包括container view和转场效果。苹果公司甚至允许开发者不传入context参数,只传入完成后执行的block。所以请大胆尝试使用它吧。
[self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(idsitionCoordinatorContext> context) { //动画结束后执?行的代码块 }];屏幕快照
[view snapshotViewAfterScreenUpdates:NO];这个方法制作了一个UIView的副本,如果我们希望视图在执行动画之前保存现在的外观,以备之后使用(动画中视图可能会被子视图遮盖或者发生其他一些变化),该方法就特别方便。
[view snapshotViewAfterScreenUpdates:YES]; [view setAlpha:0.0];由于我们设置afterUpdates参数为YES,而视图的透明度值被设置成了0,所以方法将在该设置应用在视图上了之后才进行快照,于是乎屏幕空空如也。另外就是……你可以对快照再进行快照……继续快照……