自定义过渡动画(swift 3)(本人阅读过的最好的CoreAnimation)

通过自定义过渡动画实现翻转卡片效果(Swift 3)

译者:教程和相关代码已经更新至兼容Swift 3.0,原教程中Bug已经清扫完毕,升级到Xcode 8的程序猴们可以放心观看。Swift3对CoreAnimation的API进行了大量更新,让代码简洁了不少。关于Swift3的更新,可以参考我的Swift3的变化。

 

推入、弹出、翻转….iOS内置了不少视图间的过渡动画,但自己动手显然更有趣些。自定义的过渡动画不但可以大大增强用户体验,也可以让你的App“鹤立鸡群”。如果你是一名曾经被自定义动画的复杂吓跑过的老司机,不妨也停下来看看,现在的实现方法比你想象的要简单的多。

本篇教程里,我们将给一个简单的猜猜看游戏添加过渡动画。教程结束时你将获得以下技能:

  • 了解过渡动画API的结构
  • 了解如何通过自定义过渡动画打开/关闭视图控制器
  • 了解如何创建交互式过渡

提示:本篇教程会用到UIView的动画方法,你应该具备基本的知识储备。如果没有接触过的话建议提前看一下这这篇关于iOS动画的文章,速成一下。

 

入门

老规矩,Clone或下载初始项目,编译运行一下,效果如下所示:

我们在一个Page View Controller里展示了不同的卡片。每张卡片上有一段关于宠物的描述,点击卡片会显示对应的宠物照片。

我们的任务是根据描述猜出宠物!它是喵星人,汪星人还是条咸鱼?自己把玩一下初始App看看你猜的准不准。

主要的导航逻辑为你已经写好了,但现在这个App太过普通了,没什么意思。我们通过自定义的过渡动画给它增添点色彩。

 

探索过渡动画API

过渡动画API大量地使用协议,而非实体对象。看完这一部分内容,你会了解每一个相关协议的职责,以及它们之间是如何互相联系的。下图展示了API中的一些重要主体:

 

相关主体

上图看起来挺复杂的,但当你了解了不同部分之间是如何协同工作之后,你会发现它的逻辑其实非常直接。

 

过渡协议

每一个视图控制器(View Controller)都包含一个transitioningDelegate对象,它服从UIViewControllerDelegate协议。

每当你打开或关闭一个视图控制器时,UIKit会向这个协议索取应该使用的动画控制器(Animation Controller)。如果想让协议获得的是我们自定义的对象,只需将它赋值给视图控制器的transitioningDelegate

 

动画控制器

具体实现UIViewControllerAnimatedTransitioning协议的对象,用来实现过渡动画。

 

过渡上下文

上下文负责实现UIViewControllerContextTransitioning协议,它在过渡过程中至关重要:它负责封装所有与过渡相关的视图控制器(过渡前和过渡后的)。

实际上我们并不需要自己实现这个协议。每当过渡发生时,动画控制器会自动从UIKit那里接收到已经设置好的上下文对象。

 

过渡的步骤

打开一个视图控制器时要经历以下几步:

1. 通过代码或者Segue触发过渡。

2. UIKit尝试从目标视图控制器(即将展示的)那里获取它的过渡协议。如果为空,则使用内置的标准协议。

3. UIKit通过animationController(forPresented:presenting:source:)方法向过渡协议请求动画控制器。如果方法返回nil,则使用默认动画。

4. 如果上述方法返回有效,UIKit会创建过渡上下文。

5. UIKit通过transitionDuration(using:) 方法从动画控制器那里获取动画时长。

6. UIKit在动画控制器上调用animateTransition(using:)方法,实际播放过渡动画。

7. 最后,动画控制器会调用过渡上下文的completeTransition(using:)方法,标志动画的完成。

 

自定义打开动画

是时候把刚刚新学的知识应用到实际中了!

我们的目标是实现下面的效果:

  • 当用户点击卡片,卡片翻转过来显示缩小版的Modal视图(和卡片一样大)
  • 随后放大至充满屏幕

 

创建Animator

首先需要创建动画控制器。

创建一个新的Cocoa Touch Class文件,命名为FlipPresentAnimationController,让它继承自NSObject,语言设置为Swift。点击下一步,把分组设置为Animation Controllers,然后完成创建。

动画控制器需要遵从UIViewControllerAnimatedTransitioning协议。打开FlipPresentAnimationController.swift,更新类的声明:

修改完声明后,编辑器会弹出血红的Error,提示缺失代理方法;淡定淡定,我们这不是啥都还没添加呢么,现在就来修正。

编译错误….淡定淡定

我们把卡片的框架(Frame)作为动画起点,并添加变量存储这个值:

根据协议要求,我们需要添加两个方法。

把下面的方法添加到类里:

正如名称提示的一样,该方法用于设定动画时长。暂且把它设定为2秒,以便在开发过程中观察动画效果。

接着把下面的方法声明添加到类里:

我们需要在这个方法里真正实现动画。

首先添加下面代码:

解释一下:

1. 过渡上下文负责提供与过渡相关的视图控制器,通过对应的键获取。

2. 设置“to”视图的起始帧和终止帧。过渡动画中,它从卡片大小开始,逐渐扩充并填满整个屏幕。

3. UIView捕捉“to”视图快照,并把它渲染成轻量级的视图;这样就可以在动画中同时显示当前的视图和它的父视图。快照同样从卡片的边框大小开始。此外我们把快照的边角弧度改成和卡片一样。

译者:在iOS10(Swift3)环境下,snaphotView(afterScreenUpdates:)方法无法正常获取快照。为了临时解决这个Bug,我们需要自己编写一个辅助方法,以图片的形式返回快照。

 

创建一个新的Swift文件,命名为Util,用来存放我们编写的扩展方法。添加下面代码:

然后回到之前的animateTransition(using:)方法,把注释3下面的第一行替换成下面的代码:

打完补丁我们接着往下进行。

animateTransition(using:)方法里继续添加下面的代码:

这里出现了一个新家伙:容器视图(Container View),我们可以把它想象成过渡动画的舞台。容器视图自动包含了“from”视图,我们需要自行添加“to”视图。

此外,我们还需要把快照添加到容器视图里,并隐藏实际视图。动画结束时,快照也会旋转至消失。

提示:不要被AnimationHelper唬住了,它只是一个工具类,负责给视图添加透视效果以及旋转变换。感兴趣的话可以看看它的具体实现。

 

现在,与实现动画相关的对象已经准备完毕。在方法的最后添加下面代码,具体实现动画效果:

解释一下:

1. 首先,我们设置了动画时长。注意这里transitionDuration(using:)的方法,我们在类的最开始实现了它。我们需要让动画时长和整个过渡的时长相同,以便UIKit进行同步。

2. 我们先把“from”视图沿着y轴翻转一半,让它离开画面(变成一条线了)。

3. 接着用同样的方法逐渐显示快照。

4. 然后我们让快照逐渐填满整个屏幕。

5. 一切准备完毕,我们可以放心地显示“to”视图了。快照的任务已经完成,可以把它删除了。此外我们还需要把“from”视图翻转回去,不然返回上级视图的时候就看不到它了。最后,调用completeTransition方法通知上下文,宣告动画已经完成。UIKit会确保最终状态的一致性,然后从容器里删除“from”视图。

我们的动画控制器现在可以投入使用了!

 

连接Animator

打开CardViewController.swift,在类外面添加下面的属性:

UIKit需要一个可以提供动画控制器的代理对象。因此,我们必须提供一个服从UIViewControllerTransitioningDelegate协议的对象。

在这里,我们把CardViewController当做过渡代理。在源文件末尾添加协议扩展:

在扩展里添加下面的方法:

上面的方法返回了自定义的动画控制器,同时也确保了过渡动画从正确的帧开始。

最后一步是把CardViewController设置为过渡代理。视图控制器包含一个transitioningDelegate属性,UIKit正是通过它来确定是否使用自定义过渡。

prepare(for segue:sender:)方法里添加下面的代码,就在给卡片赋值的下面:

注意,需要代理协议的是即将展示的视图控制器,而不是负责展示的。

编译运行一下我们的项目,点击卡片应该会显示如下的效果:

搞定!你的一个自定义过渡动画。别着急得意,这仅仅是一半而已:我们需要用同样浮夸的方式关闭视图。

 

自定义关闭动画

创建一个新的Cocoa Touch类,命名为FlipDismissAnimationController,确保它继承自NSObject并隶属于Animation Controllers组下。

往新文件里添加下面的代码:

这个类需要实现打开动画的逆转版:

  • 把当前页面缩小为卡片大小;我们用destinationFrame存储这个值。
  • 翻转视图以显示原始卡片。

animateTransition(_:)方法里添加下面的代码:

你应该已经熟悉这些步骤了,这里还是再重复解释一下:

1. 因为在这个动画里,我们需要缩小视图,所以初始帧和终止帧和之前正好相反。

2. 这次我们操作的是“from”视图,所以快照应该通过它来创建。

3. 和之前一样,我们把“to”视图以及快照添加到容器视图里,并隐藏“from”视图以免和快照冲突。

4. 最后,我们通过翻转隐藏“to”视图。

剩下的就是添加动画本身了。

紧接着之前的代码,在animateTransition(_:)里添加下面的代码:

这就是之前动画的逆转版本:

1. 首先缩放视图,然后通过翻转隐藏快照。接着通过反方向旋转逐渐显示“to”视图。

2. 最后,我们删除快照并通知上下文,过渡动画已经完成。这样UIKit就知道可以开始更新视图控制器的层级结构,并删除过渡时使用的视图了。

打开CardViewController.swift,在动画控制器下面添加一个新的属性:

接着,往协议扩展里添加下面的方法:

和之前类似,负责把正确的视图框架传递给动画控制器,并返回这个控制器。

最后一步,修改FlipPresentAnimationController里的transitionDuration(using:)方法,把它的速度改成和关闭动画一致:

编译运行你的App,点击卡片看看现在的打开/关闭动画:

非常犀利的自定义动画!但为了追求完美,我们更进一步,给动画添加交互。

 

添加交互

译者:如果你看过我之前几篇教程,这部分内容可以跳过。不妨回忆一下思路,尝试独立完成。

 

iOS自带的设置软件是交互式过渡动画的典范。这一节我们的任务是,利用左边缘滑动手势,退回卡片朝下的状态,过渡动画跟随用户手势。

 

交互式过渡的原理

交互控制器(Interaction Controller)可以响应触摸事件以及编码控制,比如加速、减速,甚至反转过渡动画。为了添加交互,过渡代理必须负责额外提供一个交互控制器。它可以是任何实现了UIViewControllerInteractiveTransitioning协议的对象。我们已经编写好了过渡动画,交互控制器只是负责让这个动画跟随你的手势,而不是像播放视频一样,从头到尾直接放完。

Apple提供了一个现成的UIPercentDrivenInteractiveTransition类,它是一个具体的交互控制器的实现。我们正好可以利用它,给我们的过渡添加交互。

 

创建交互式过渡

首先我们需要创建一个交互控制器。新建一个Cocoa Touch Class文件,命名为SwipeInteractionController,让它继承自UIPercentDrivenInteractiveTransition。确保语言为Swift,添加到Interaction Controllers组里。

打开SwipeInteractionController.swift,在类定义的最开始添加下面这些属性:

这些属性的作用显而易见:

  • 正如其名,interactionInProgress用于指示交互是否在进行中。
  • 我们需要在内部使用shouldCompleteTransition来控制过渡,随后你会看到使用方法。
  • 交互控制器直接负责打开/关闭视图控制器,所以我们需要把当前的视图控制器存储在viewController里,便于引用。

把下面的方法添加到类里:

我们需要通过手势控制过渡动画。在上面的方法里,我们获得了视图控制器的引用,并给它的视图添加了手势识别器。

如下添加prepareGestureRecognizerInView(_:)方法:

我们定义了一个手势识别器,通过左边缘滑动手势触发,并把它添加到视图上。

最后一步是添加handleGesture(_:)方法,如下所示:

你可能感兴趣的:(iOS,animation,swift)