前言
iOS7之后,Apple在自定义转场动画方面上新增了许多API,让开发者更加快捷高效地自定义视图控制器间pop/push,present/dismiss,或者tabbarcontroller子控制器间的切换.而之后也正是基于这些API,让许多App有了许多incredible转场动画效果.该Session发布于2013年整体介绍了关于新增转场动画相关的API和以及如何利用这些API基本实现原理和注意,以下为所涉及的内容:
- 新动画相关API
- 自定义视图控制器转场
- 交互式视图控制器转场
- 转场过程的控制处理
内容
新动画相关API
基础动画API
iOS5时Apple就增加了许多与基于Block的UIView动画API.众所皆知,其UIView动画API本质是对CoreAnimation API的高级封装,满足了大多数简单动画效果的实现.
如上图所示,在UIView动画的Block内更新视图的可动画属性,本质会转换为在该视图的layer上进行相应属性更新,创建相应CAnimation动画对象,并添加到该layer上,最后我们就能看到block内的视图的动画效果了.
现在针对视图属性的变化但不想产生动画效果,甚至是在Block动画API中,也禁止产生动画,UIView有了新的Block API:
performWithoutAnimation(actionsWithoutAnimation: () -> Void)
//动画过程:直接进行performWithoutAnimation内部Block的视图属性更新,且无动画效果;
//再具体执行animateWithDuration内的视图动画.
UIView.animateWithDuration(1.0) { () -> Void in
self.subView.alpha = 0.5;
UIView.performWithoutAnimation({ () -> Void in
self.subView.center = self.view.center
})
}
Spring Animation
为了创建更加有活力的,具有物理弹性效果的动画,引入了基于Block的Spring动画API(对CASpringAnimation动画API高级封装,CASpringAnimation在iOS9才开放给开发者):
animateWithDuration(duration:delay:dampingRatio:velocity:options:animations:completion:
- dampingRatio 表示阻尼比例,取值0~1,阻尼比例越小,振荡幅度越大.
- velocity 用于计算Spring动画开始时动画元素的初速度,一秒内所位移距离与velocity相乘获得初速度,例如在1s时间内,Spring动画的总距离是200point,你想要动画的开始,以匹配一个视图开始速度为100 point/s(200 point * 0.5 / 1 s),设置velocity值为0.5即可。
//Spring动画示例
UIView.animateWithDuration(1.0,
delay: 0.0,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.5,
options: .OverrideInheritedOptions,
animations: { () -> Void in
self.subView.center = self.view.center
}, completion: nil)
Key-frame 动画API
如同UIView的基本动画BlcokAPI是对CABasicAnimation的高级封装,基于Block的帧动画API就是对于CAKeyframeAnimation的UIKit层面的封装:
public class func animateKeyframesWithDuration(duration: NSTimeInterval, delay: NSTimeInterval, options: UIViewKeyframeAnimationOptions, animations: () -> Void, completion: ((Bool) -> Void)?)
public class func addKeyframeWithRelativeStartTime(frameStartTime: Double, relativeDuration frameDuration: Double, animations: () -> Void)
针对每一帧的动画参数设置在整体的Block内使用
addKeyframeWithRelativeStartTime..
方法进行配置,其中frameStartTime
和frameDuration
取值都在0~1之间,都是相对整体duration的比例值,根据frameStartTime
得到相对于整体动画时间的时间点,表示在整个动画duration中该帧动画执行时机;而根据frameDuration
得到相对于整体动画时间的持续时间,表示该帧将执行动画时持续的时间,但超过整体动画duration的剩余时间时,系统会按照其剩余时间作为帧动画持续时间使用.
// 示例
UIView.animateKeyframesWithDuration(4.0,
delay: 0.0,
options: .AllowUserInteraction,
animations: { () -> Void in
//Frame 1 after 2s(4*0.5) start,duration: 0.8s(4*0.2), remain: 1.2s(4-2-0.8)
UIView.addKeyframeWithRelativeStartTime(0.5,
relativeDuration: 0.2,
animations: { () -> Void in
self.subView.center = CGPointMake(self.view.center.x, 0)
})
// Frame 2 after 2.8s(2.0+0.8 <=> 0.7*4) start,duration: 1.2s(0.3*4 <=> 4-2.8), remain: 0.0s
UIView.addKeyframeWithRelativeStartTime(0.7,
relativeDuration: 0.3,
animations: { () -> Void in
self.subView.center = self.view.center
})
},
completion: nil)
Snapshot API
为了能更方便使用动画元素,增加了UIView关于快照生成的API,所生成的快照视图可以使用在转场动画中.
view.snapshotViewAfterScreenUpdates(Bool)
view.resizableSnapshotViewFromRect(CGRect, afterScreenUpdates: Bool, withCapInsets: UIEdgeInsets)
其中第二个方法可以更加自由地选择快照区域.
UIDynamic API
关于UIDynamic相关动画API由于有专门Session进行演示和说明,因此没有进行详细的介绍,只是提到用于辅助视图产生多种动态,更接近物理变化效果的动画API,在自定义控制器转场动画有许多高级的用法.以后会针对UIDynamic相关Session进行记录.
自定义视图控制器转场
设置自定义的转场
- Presention and dismissals(针对模态视图的转场)
- 设置ModalPresentationStyle,自定义时赋值为FullScreen或Custom(区别:赋值Custom时FromViewController(来源控制器)的视图不会从当前窗口层次中移除,仍然存在.)
- 设置from控制器的transitioningDelegate
- 调用presentViewController/dismissViewController
- Tabbarcontroller的子控制器切换转场
- 设置tabBarControllerDelegate代理对象
- 调用setSelectedIndex/setSelectedController
- NavigationControler的push/pop转场
- 设置navigationControllerDelegate代理对象
- 调用pushVieController/popViewController
- push/pop下CollectionController布局切换时的转场
- 设置需要定义转场效果的collectionController的useLayoutToLayoutNavigationTransition属性为true
- 调用pushVieController/popViewController
Concepts & API
- 所有转场动画效果都将在遵守
UIViewControllerContextTransitioning
协议的context下进行,你可以通过context的系列方法如viewControllerForKey
/viewForKey
获得from/to相关的视图控制器和视图,也可以通过containerView
获得转场过渡的容器视图,转场相关的视图都必须加入到该视图层次中,并且根据转场需要来设置两者层次顺序,最后转场动画执行结束后为了让视图和控制器层次一致而必须要调用completeTransition(Bool)
方法,通知系统转场是否完成. - 而转场动画相关context,只能在遵守
UIViewControllerAnimatedTransitioning
协议方法中返回,因此无论自定义哪种转场都必须返回各自的实现UIViewControllerAnimatedTransitioning
协议方法的自定义转场对象. - Apple给出了常规情况下针对控制器间视图转场所涉及的代理对象
- 针对Tabbar视图: UITabBarControllerDelegate (新增了方法)
- 针对导航视图: UINavigationControllerDelegate (新增了方法)
- 针对模态视图: UIViewControllerTransitioningDelegate (新增该类)
只要赋值其各自的代理对象,并在其代理方法中返回自定义转场对象,就可以实现非交互式的转场动画.
(若要使转场动画可交互则允许交互的控制器的代理对象中除此之外,还要再返回一个实现了UIViewControllerInteractiveTransitioning
协议方法的交互式转场对象)
交互式视图控制器转场
- 现在应用程序中最常见控制器间交互式转场的就是pop/push时基于手势驱动的交互转场了.
- 现在自定义交互式转场也不一定需要手势驱动,自定义其他驱动方法如:摆放位置,加速度变化...;并且交互式转场必须可以被开始和被停止.
- UIKit提供了一个交互控制器类
UIPercentDrivenInteractiveTransition
,让开发者能以此继承,来简单配置百分比驱动的交互转场动画.而使用上,首先转场动画必须在UIView的BlockAPI中进行,其次提供一个继承于UIPercentDrivenInteractiveTransition
的实例中,最后在逻辑代码中调用其update/cancel/finish相关方法,start方法由系统内部调用.
Note: 交互式转场对象的使用,必须在自定义转场对象存在的前提下使用,只有已经实现了返回自定义转场对象的方法,系统才会查询是否存在交互式转场对象进行使用.
Coordinator与转场过程的控制
在交互式动画转场过程中,由于其交互性而系统则时刻监听着转场的进行状态:
- start状态
- update状态
- end 状态
- complete状态
- cancel状态
在这些状态的变化中,实际上代表转场间两种的视图控制器的视图生命周期变化,如图:
这里重点提到了由于交互转场中cancel状态的出现,会使得视图控制器的
viewDidAppear
方法不一定会在
viewWillAppear
后调用,之后而是调用
viewWillDisappear
,对于
viewWillDisappear
来说也如此,之后不一定会去调用
viewDidDisappear
.
因此为了当交互转场被取消时仍能要调用
viewDiDAppear
方法中的转场完成后的清理处理代码,Apple引入了
UIViewControllerTransitionCoordinator
协议对象transitionCoordinator,通常让其在
veiwWillAppear
中利用
notifyWhenInteractionEndsUsingBlock(handler:)
方法在交互状态为取消时,执行原本在
viewDiDAppear
方法中的转场完成后的清理处理的代码,来完成这次交互转场的取消.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let transitionCoordinator = self.transitionCoordinator()
transitionCoordinator?.notifyWhenInteractionEndsUsingBlock({ (ctx) -> Void in
if ctx.isCancelled() {
// do some clean code in viewDidAppear
}
})
}
无论哪种转场方式,其控制器都会有个transitionCoordinator(利用UIViewController的transitionCoordinator()
获得),也可以利用它来执行转场完成后的handle回调;使用协议的animateAlongsideTransition..
系列方法,还可以执行出转场动画之外的其他动画,让两种动画是同时进行的.
//导航控制器push后利用transitionCoordinator的方法执行completion handle.
self.navigationController?.pushViewController(self, animated: true)
let transitionCoordinator = self.navigationController?.transitionCoordinator()
transitionCoordinator?.animateAlongsideTransition(nil, completion: { (ctx) -> Void in
// do completion handle
})
总结
初看这个Session,一系列超长命名的协议以及协议方法有点让人眼花缭乱,还有点晕,但通过一点点回放细听,并且加上思考理解,看完这个Session对转场中视图生命周期,转场过程,以及转场过程中重要阶段有了未曾深入的了解,也对于转场的整体把握和注意点有了大致的了解和体会,收获不小O(∩_∩)O哈!