感谢翻译小组成员
dingdaojun热心翻译。本篇文章是咱们每周推荐优秀国外的技术类文章的其中一篇。若是您有不错的原创或译文,欢迎提交给咱们,更欢迎其余朋友加入咱们的翻译小组(联系qq:2408167315)。
在iOS7之前,开发者若是但愿定制导航控制器推入推出视图时的转场动画,通常都只能经过子类化UINavigationController或者本身编写动画代码去覆盖相应的方法,如今iOS7为开发者带来了福音,苹果公司引入了大量新API,给予了开发者很高的自由度,在处理由UIViewController管理的UIView动画时,这些API使用方便,可扩展性也很强,定制起来很是轻松:
• 全新的针对UIView的动画block方法
• 全新的UIViewControllerAnimatedTransitioning协议以及动画控制器的概念
• Interaction Controllers以及Transition Coordinators
• 全新的针对动画的助手API(简便方法)
这里我编写了一个示例应用程序,其中展现了我将在这篇文章中所提到的一些技巧, 为了快速理解咱们应当如何使用iOS7的新API来处理
UIViewController的转场动画,请在此连接中下载该示例。
全新的针对UIView的动画block方法
iOS4的发布带来了强大的block方法,在编写UIView动画时使用block能够轻松地获得满意的效果,然而有些状况下,咱们仍是不得不直接使用Core Animation。幸运的是,苹果公司在iOS7中增长了2个新的基于block的方法,这样咱们就不多再须要直接使用Core Animation了。
关键帧动画
iOS7为UIView封装了一组API,让咱们很容易的获得与Core Animation框架中的CAKeyframeAnimation同样的效果。
- [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依次将全部关键帧加入动画执行栈中。
下面是一个简单的例子:在示例应用中,我使用关键帧block来退出模态视图控制器。
- [UIView addKeyframeWithRelativeStartTime:0.0
- relativeDuration:0.15 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- -1.5);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.15
- relativeDuration:0.10 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.0);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.25
- relativeDuration:0.20 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.3);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.45
- relativeDuration:0.20 animations:^{
-
- 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;
- }];
视图仿佛在重力的牵引下绕左下角顺时针旋转,并在最低点摆动了一下,最后脱落。
弹簧动画
iOS7新引入的另外一个block方法可让你轻松将真实物理世界中的弹性效果集成进视图动画中。苹果公司一直建议开发者尽量将动画效果作的跟真实物理世界同样——在视图滑动时,能够像弹簧同样,稍微拉伸一些,再弹回正确位置。使用新的弹簧动画API来实现此效果相较以往要简单不少。
- [UIView animateWithDuration:duration delay:delay
- usingSpringWithDamping:damping initialSpringVelocity:velocity
- options:options animations:^{
-
- } completion:^(BOOL finished) {
-
- }];
这里用到了一些物理上的概念:damping参数表明弹性阻尼,随着阻尼值愈来愈接近0.0,动画的弹性效果会愈来愈明显,而若是设置阻尼值为1.0,则视图动画不会有弹性效果——视图滑动时会直接减速到0并马上中止,不会有弹簧类的拉伸效果。
velocity参数表明弹性修正速度,它表示视图在弹跳时恢复原位的速度,例如,若是在动画中视图被拉伸的最大距离是200像素,你想让视图以100像素每秒的速度恢复原位,那么就设置velocity的值为0.5。(译者:建议你们看看源代码,代码中damping设置为0.8不够明显,你能够将damping调为0.1,而后慢慢调整velocity看看效果)
在示例应用程序中,我用弹簧动画让模态视图控制器从屏幕底部滑上来,设置弹性阻尼为0.8,弹性修正速度为1.0,运行后能够看到,视图将冲出15像素的距离,而后慢慢降回原位。若是我设置弹性阻尼为0.6或者更小,那么视图会冲得更高,并且降回原位前还会继续向下反弹。(也就是中止前来回弹的次数愈来愈多,弹性效果愈来愈明显)须要注意的是,不要将弹性动画与UIKit的动态特效引擎相混淆。弹性动画是一个标准的UIView动画API,仅仅提供了有限的几种真实物理效果。
自定义UIViewController的转场动画
如今让咱们来看一个好东西。苹果公司不只为开发者引入了新的动画API,并且还扩大了其应用范围。在使用UIViewController管理视图的推入推出时,能够很容易地自定义如下转场动画:
• UIViewController
• presentViewController
• UITabBarController
• setSelectedViewController
• setSelectedIndex
• UINavigationController
• pushViewController
• popViewController
• setViewControllers
在示例应用程序中,我建立了一系列转场动画,在动画中使用了以前讲解过的新引入的弹簧动画和关键帧block方法,如今让咱们来看看如何使用新API来自定义上述的转场动画。
核心概念:动画控制器
那么,如何在使用自定义动画的同时不影响视图的其余属性?对此苹果公司提供了一个新的协议:UIViewControllerAnimatedTransitioning,咱们能够在协议方法中编写自定义的动画代码。苹果开发者文档中称实现了此协议的对象为动画控制器。
因为咱们使用了协议这一语法特性,自定义动画的代码能够灵活的放在本身想要的位置。你能够建立一个专门用于管理动画的类, 也可让UIViewController实现UIViewControllerAnimatedTransitioning接口。因为须要实现一系列不一样的动画,所以选择为每一个动画建立一个类。接下来建立这些动画类的通用父类——BaseAnimation,它定义了一些通用的属性和助手方法。
让咱们来看第一个动画,使用UINavigationController推入推出视图时,会有一个简单的缩放效果。
- -(void)animateTransition:
- (id)transitionContext {
-
- UIView *containerView = [transitionContext
- containerView];
- UIViewController *fromViewController = [transitionContext
- viewControllerForKey:UITransitionContextFromViewControllerKey
- ];
- UIViewController *toViewController = [transitionContext
- viewControllerForKey:UITransitionContextToViewControllerKey];
- if (self.type == AnimationTypePresent) {
-
- toViewController.view.transform =
- CGAffineTransformMakeScale(0.0, 0.0);
- [containerView insertSubview:toViewController.view
- aboveSubview:fromViewController.view];
-
- [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) {
-
- [containerView insertSubview:toViewController.view
- belowSubview:fromViewController.view];
-
- [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,很是方便。
在animateTransition:中你须要处理如下过程:
1. 将“to”视图插入容器视图
2. 将“to”和“from”视图分别移动到本身想要的位置
3. 最后,在动画完成时千万别忘了调用completeTransition: 方法
UIViewControllerAnimatedTransitioning协议中的方法都带有一个参数:transitionContext,这是一个系统级的对象,它符合 UIView-ControllerContextTransitioning协议,咱们能够从该对象中获取用于控制转场动画的必要信息,主要包括如下内容:
显然,苹果公司帮助开发者完成了大部分让人讨厌的细节工做,仅仅须要咱们本身完成的工做就是定义动画的初始状态和终止状态,并调整到本身满意的效果。最后我再啰嗦两句有关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。
为转场动画定义交互方式
在iOS7中,苹果处处都在使用交互式弹出手势,同时,苹果也给开发者们提供了一系列工具,只需简单几步就能将交互手势应用在视图切换过程当中。咱们能够经过相应的委托方法返回一个交互控制器:
• UINavigationController
• interactionControllerForAnimationController
• UITabBarController
• interactionControllerForAnimationController
• UIViewController
• interactionControllerForPresentation
• interactionControllerForDismissal
这里惟一须要注意的是,若是没有自定义转场动画,这些方法就不会起做用。例如,你必须从animationControllerForOperation获得一个有效的动画控制器,UINavigationController才会调用interactionController-
ForAnimationController——即便你在转场交互中没有使用动画控制器。
其次,交互控制器很是灵活,有很强的可扩展性。虽然在示例应用程序中我使用手势检测来控制交互,可是你也能够用手势之外的其余方式来实现。你能够设计任意你想要的效果用以转场交互。
交互控制器:最简单的实现方式有两种方式能够建立交互控制器。第一个也是最简单的一个,就是使用UIPercentDrivenInteractiveTransition。
- @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属性来修改其余的一些样式。
交互控制器:经过自定义的方式
若是你须要深刻控制UIPercentDrivenInteractiveTransition处理转场动画的细节,那么就不用去继承该类,而是使用UIViewController-InteractiveTransitioning协议。此协议与UIViewController-AnimatedTransitioning相似,咱们能够经过该协议控制全部关于转场动画的细节。在该协议中咱们须要完成如下步骤:
1. 实现startInteractiveTransition:方法,用于初始化专场动画。
2. 获取transitionContext 对象的引用(若是继承了UIPercentDrivenInteractiveTransition,能够看到它自动帮咱们完成了这一步骤,所以这里咱们必须手动获取该对象)。
3. 和以前同样,在适当的状况下调用updateInteractiveTransition:,cancelInteractiveTransition和finishInteractiveTransition(对于导航控制器来讲,完成方法中还须要显示或隐藏导航栏)。
4. 完成后仍然请记住调用transitionCompleted:。
下面是我经过自定义的交互控制器来实现与以前相同的动画,仍然是使用捏合手势控制转场动画。
- -(void)startInteractiveTransition:
- (id)transitionContext {
-
- _context = transitionContext;
-
- UIView *containerView = [transitionContext
- containerView];
- UIViewController *fromViewController = [transitionContext
- viewControllerForKey:UITransitionContextFromViewControllerKey
- ];
- UIViewController *toViewController = [transitionContext
- viewControllerForKey:UITransitionContextToViewControllerKey];
-
- 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(像示例程序中那样),从而把全部代码都放在一个类中。你也能够将交互控制器和动画控制器分红两个类——协议这一语法特性的妙处在于,你能够轻松实现符合需求的最佳解决方案。
更多小技巧
在block中选择是否进行动画
开发者或许会遇到这样一种状况:在一串精美的动画效果中,咱们须要让某些视图不进行动画,从而营造一种动静相宜的效果。在动画block方法推出以前,咱们能够在[UIView beginAnimations]和[UIView commitAnimations]之间使用setAnimationsEnabled方法来设置哪些动画不须要执行。而在iOS7SDK中,苹果公司为开发者提供了新方法,只要把不须要执行的动画写在block中便可:
- [UIView performWithoutAnimation:^{
-
- }];
你能够随时执行这段代码来控制不须要执行的动画。
集合视图的导航转场动画
你可能对UICollectionView的setLayout:animated:方法很是熟悉了。在iOS7中,当导航控制器推入推出集合视图控制器时,若是开启了 useLayout-ToLayoutNavigationTransitions属性,系统将自动调用setLayout:animated:方法。所以,在你推入集合视图控制器时,只须要设置该属性,导航控制器就能够自动执行动画,和你手动对集合视图调用setLayout:animated方法的效果同样。
- CollectionViewController *VC = [[CollectionViewController
- alloc] initWithCollectionViewLayout:flowLayout];
- VC.title = @"Mini Apples";
- VC.useLayoutToLayoutNavigationTransitions = YES;
- [self.navigationController pushViewController:VC
- animated:YES];
转场动画调度器
还有一个很是有用的API, 它能够帮助视图控制器管理转场动画:UIViewControllerTransitionCoordinator协议。在iOS7中,每个视图控制器(固然也包括UINavigationController和UITabBarController)都有一个transitionCoordinator属性,该属性提供了一系列用于转场动画的强大工具,首先咱们来看看animateAlongsideTransition:方法。
- [self.transitionCoordinator
- animateAlongsideTransition:^(id<uiviewcontrollertransitioncoo < span="">
- rdinatorContext> context) {
-
- }
- completion:^(id
- context) {
-
- }];
咱们能够经过这个方法在进行转场动画时并行执行一些其余动画,context参数和以前提到的符合UIViewControllerContextTransitioning协议的transitionContext参数相相似,从该参数中咱们能够获取有关转场过程的一些重要信息,包括container view和转场效果。苹果公司甚至容许开发者不传入context参数,只传入完成后执行的block。因此请大胆尝试使用它吧。
对于交互转场来讲, 视图在转场过程当中状态可能发生改变, 因而notifyWhenInteractionEndsUsingBlock:方法特别有用——它能够用来管理视图状态。在交互转场中,viewWillAppear:方法或许会在某个视图控制器推入时被调用,可是按照常理随后应该会被调用的viewDidAppear:则不必定,由于用户随时可能取消该交互(例如在以前的例子中,捏到一半又恢复原状)。
由此,若是咱们不但愿在这种状况下修改视图状态,咱们可使用该方法,恢复对视图的更改(使用UIViewControllerTransitionCoordinatorContext的isCancelled属性)。
- [self.transitionCoordinator
- notifyWhenInteractionEndsUsingBlock:^(id<uiviewcontrollertran < span="">
- sitionCoordinatorContext> context) {
-
- }];
屏幕快照
在iOS7 之前, 获取一个UIView的快照有如下步骤: 首先建立一个UIGraphics的图像上下文,而后将视图的layer渲染到该上下文中,从而取得一个图像,最后关闭图像上下文,并将图像显示在UIImageView中。如今咱们只须要一行代码就能够完成上述步骤了:
- [view snapshotViewAfterScreenUpdates:NO];
这个方法制做了一个UIView的副本,若是咱们但愿视图在执行动画以前保存如今的外观,以备以后使用(动画中视图可能会被子视图遮盖或者发生其余一些变化),该方法就特别方便。
afterUpdates参数表示是否在全部效果应用在视图上了之后再获取快照。例如,若是该参数为NO,则立马获取该视图如今状态的快照,反之,如下代码只能获得一个空白快照:
- [view snapshotViewAfterScreenUpdates:YES];
- [view setAlpha:0.0];
因为咱们设置afterUpdates参数为YES,而视图的透明度值被设置成了0,因此方法将在该设置应用在视图上了以后才进行快照,因而乎屏幕空空如也。另外就是……你能够对快照再进行快照……继续快照……
结论
苹果公司在iOS7中为开发者添加了新的用于建立和自定义动画的API。iOS7 SDK不只引入了强大的动画block和许多易于使用的方法,并且完全改变了为视图自定义动画的方式。最后,强烈建议你将示例应用程序的代码下载下来,看看我在本文中介绍的API的使用方法!