自定义转场动画
这张图是自己在翻译官方文档Customizing the Transition Animations
的总结:
接下来是漫长的翻译部分,英文水平有限,有些单词不知道怎么去表达(比如persentations
,dismissals
,chrome
等等),各位看官多多指教,在下感激不尽。
自定义转场(过渡)动画
转场动画可提供应用程序界面变更的视觉反馈。UIKit提供了一套标准转场样式,以便在呈现视图控制器的时候使用,并且你可以使用自己的自定义转场来补充标准转场。
转场动画序列
转场动画将一个控制器的内容交换为另一个的内容。有两种类型的转场:presentations
和dismissals
。一个presentations
转场会向应用程序的视图控制器层级添加一个新的视图控制器,而dismissal转场会从层级中移除一个或多个视图控制器。
实现转场动画需要许多对象。UIKit
提供转场中涉及的所有对象的默认版本,你可以自定义所有对象或者仅定义一个子集。如果你选择正确的对象集,你应该能够只用少量的代码创建你的动画。即使包含交互的动画,如果你利用UIKit提供的现有代码,也可以轻松地实现。
转场的代理
转场代理是转场动画和自定义presentations
的起点。转场代理是一个你定义的并遵守UIViewControllerTransitioningDelegate
协议的对象。它的工作是为UIKit提供以下对象:
- 动画对象。动画对象是负责创建用于显示或隐藏一个视图控制器的视图的动画。转场代理可以提供用于
presenting
和dismissing
视图控制器的单独的动画对象。动画对象遵守UIViewControllerAnimatedTransitioning
协议。 - 交互式动画对象。交互式动画对象使用触摸事件或者手势识别器来驱动自定义动画的定时。交互式动画对象遵守
UIViewControllerInteractiveTransitioning
协议。创建交互式动画的最简单方法是将UIPercentDrivenInteractiveTransition
子类化,并向你的子类添加事件处理代码。该类控制使用现有动画对象创建的动画的时间。如果你创建自己的交互式动画,你必须自己渲染动画的每个帧。 - 呈现的控制器(
persentation controller
)。在视图控制器在屏幕上时呈现控制器管理着呈现风格。系统提供了内置呈现样式的呈现控制器,你可以为你自己的呈现样式提供自定义呈现控制器。有关创建自定义的呈现控制器的更多信息,请看Creating Custom Presentations
.
给视图控制器的transitioningDelegate
赋值一个转场代理对象,告诉UIKit你想要执行自定义转场或呈现。你的代理可以选择它提供的哪些对象。如果你不提供动画对象,UIKit
会在视图控制器的modalTransitionStyle
属性中使用标准的转场动画。
图10-1显示转场代理和动画对象与被呈现视图控制器的关系。presentation controller
仅在视图控制器的modalPresentationStyle
的属性设置为UIModalPresentationCustom
时使用。
有关如何实现你的转场代理的更多信息,请看Implement the Transitioning Delegate.
有关转场代理对象的方法的更多信息,请看UIViewControllerTransitioningDelegate Protocol Reference
.
转场动画序列
当一个被呈现的视图控制器的transitioningDelegate
属性包含一个有效的对象时,UIKit
会使用你提供的自定义动画对象呈现那个视图控制器。在准备呈现时,UIKit
会调用你的转场代理的animationControllerForPresentedController:presentingController:sourceController:
方法来获取自定义动画对象。如果一个对象是有效的,UIKit
会执行下面的步骤:
- 如果一个交互式动画对象是有效的,
UIKit
会调用转场代理的interactionControllerForPresentation:
方法。如果该方法返回nil
,UIKit
会执行没有用户交互的动画。 -
UIKit
会调用动画对象的transitionDuration:
方法来获取动画时长。 -
UIKit
会调用恰当的方法来开始动画:- 对于非交互式动画,
UIKit
会调用动画对象的animateTransition:
方法 - 对于交互式动画,
UIKit
会调用交互式动画对象的startInteractiveTransition:
方法。
- 对于非交互式动画,
-
UIKit
等待一个动画对象调用上下文转场对象的completeTransition:
方法。你的自定义动画对象在动画完成后调用这个方法,尤其是在动画的完成回调里。调用这个方法来结束转场,并且让UIKit
知道它可以调用presentViewController:animated:completion
的完成处理和调用动画对象自己的animationEnded:
方法。
当dismissing
一个视图控制器时,UIKit
调用你的转场代理的animationControllerForDismissedController:
方法并执行下面的步骤:
- 如果一个交互式动画对象时有效的,
UIKit
会调用转场代理的interactionControllerForDismissal:
方法。如果方法返回nil
,UIKit
会执行没有用户交互的动画。 -
UIKit
会调用动画对象的transitionDuration:
方法来获取动画时长。 -
UIKit
会调用恰当的方法来开始动画:- 对于非交互式动画,
UIKit
会调用动画对象的animateTransition:
方法 - 对于交互式动画,
UIKit
会调用交互式动画对象的startInteractiveTransition:
方法。
- 对于非交互式动画,
-
UIKit
等待一个动画对象调用上下文转场对象的completeTransition:
方法。你的自定义动画对象会在他的动画完成后调用这个方法,尤其是在动画的完成回调里。调用这个方法来结束转场,并让UIKit
知道它可以调用presentViewController:animated:completion:
方法的完成处理回调和调用动画对象自己的animationEnded:
方法。
注意:在你的动画结束时调用
completeTransition:
方法是必须的。UIKit
不会结束转场过程,从而将控制权返回到你的应用程序,直到你调用了该方法。
转场上下文对象
在转场动画开始前,UIKit
会创建一个转场上下文对象,并给它填充关于如何执行动画的信息。转场上下文兑现是你的代码中重要的一部分。它实现了UIViewControllerContextTransitioning
协议,并储存着在转场中视图控制器和视图相关的引用。它也储存着一些信息,关于你应该如何执行转场,包括动画是否是交互的。你的动画对象需要所有的这些信息来设置和执行真实的动画。
注意:在设置自定义动画时,总是使用转场上下文对象中的对象和数据而不是使用你管理自己的缓存信息。转场在多种条件下会发生,有一些可能会改变动画的参数。转场上下文对象保证了你需要执行动画的正确信息,而你的缓存信息可能会在你调用动画的方法时失效。
图10-2显示转场上下文对象如何和其他对象交互。你的动画对象在它的animateTransition:
方法中接受对象。你创建的动画应该在提供的容器视图中进行。例如,当正在呈现一个视图控制器时,添加它的视图作为这个容器视图的子视图。这个容器视图可能会是窗口或者一个有规律的视图,但是它总是被配置来运行你的动画。
图:
有关转场上下文对象的更多信息,请看UIViewControllerContextTransitioning Protocol Reference
.
转场协调器
对于内置的转场和你自定义的转场,UIKit
会创建一个转场协调器对象来促进任何你可能需要执行的特别的动画。除了视图控制器的presentation
和dismissal
外,转场会在界面发生旋转或视图控制器的frame
改变时产生。所有这些转场代表着视图层级的改变。转场协调器是一种方式来跟踪这些变化,并同时使你的内容动画。访问转场协调器,从受影响的视图控制器的transitionCoordinator
属性中获取该对象。转场协调器只在转场过程中存在。
图10-3显示转场协调器与有关presentation
的视图控制器的关系。使用转场协调器来获取转场的信息,并注册要与转场动画同时执行的动画块。转场协调器对象遵守UIViewControllerTransitionCoordinatorContext
协议,它提供定时信息,动画的当前状态信息,在转场中涉及的视图和视图控制器。当你的动画块被执行时,它一样会收到一个上下文对象带有相同的信息。
有关转场协调器对象的更多信息,请看UIViewControllerTransitionCoordinator Protocol Reference
。有关你可以用来配置你的动画的上下文信息,请看UIViewControllerTransitionCoordinatorContext Protocol Reference
.
使用自定义的动画呈现一个视图控制器
使用自定义的动画来呈现一个视图控制器,需要做以下你的视图控制器的动作方法:
- 创建一个你想要呈现的视图控制器
- 创建你的自定义的转场代理对象,并把它赋值给视图控制器的
transitioningDelegate
属性。你的转场代理的方法应该当调用时创建和返回你的自定义动画对象。 - 调用
presentViewController:animated:completion:
方法来呈现这个视图控制器。
当你调用presentViewController:animated:completion:
方法时,UIkit
会启动呈现过程。Presentations
在下一个运行循环迭代期间开始并继续,直到你的自定义动画调用了completeTransition:
方法。交互式转场允许你在转场正在进行时处理触摸事件,但非交互式转场在由动画对象指定的持续时间内运行。
实现转场代理
转场代理的作用是用来创建和返回你的自定义对象。表10-1显示了转场方法的实现可以如此简单。这个例子创建并返回了一个自定义动画对象。大多数实际工作都是由动画对象自己来处理。
//Listing 10-1Creating an animator object
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source {
MyAnimator* animator = [[MyAnimator alloc] init];
return animator;
}
转场代理的其它方法可以像上面清单上的方法一样简单。你也可以结合自定义的逻辑,根据你的应用程序的当前状态返回不同的动画对象。有关转场代理的方法的更多信息,请看UIViewControllerTransitioningDelegate Protocol Reference
.
实现你的动画对象
一个动画对象是任何遵守了UIViewControllerAnimatedTransitioning
协议的对象。动画对象创建在固定时间段内执行的动画。动画对象的关键是它的animateTransition:
方法,你用来创建真实的动画。动画的过程大致分为以下几个部分:
- 获取动画参数
- 使用
CoreAnimation
或UIView
的动画方法创建动画 - 清理并完成转场
获取动画参数
传递给你的animateTransition:
方法的上下文转场对象包含在执行你的动画时使用的数据。在你可以从上下文转场对象获取更多最新数据信息时,不要使用你自己的缓存信息或者从你的视图控制器请求到的信息。Presenting
和dismissing
视图控制器有时候涉及到你的视图控制器之外的对象。例如,一个自定义的呈现控制器可能会添加一个背景视图作为呈现的一部分。上下文转场对象考虑了额外的视图和对象,并为您提供了动画的正确视图。
- 调用两次
viewControllerForKey:
方法,以获取转场中涉及的”from”和”to”视图控制器。不要假设你知道哪些视图控制器参与了转场。UIKit可能会在适应新的特殊环境或响应你的应用程序请求时更换视图控制器。 - 为了动画调用
contrainerView
方法来获取父视图。给这个视图添加关键的子视图。例如,在呈现期间,给这个视图添加被呈现的视图控制器的视图。 - 调用
viewForKey:
方法来获取要添加或删除的视图。视图控制器的视图在转场过程中可能不是唯一的要被添加或被移除。presentation
控制器可能插入视图到视图层级中,也必须被添加或移除。viewForKey:
方法会返回包含所有你需要添加或移除的根视图。 - 调用
finalFrameForViewController:
方法可以获取要添加或移除的视图的最终的frame
矩形。
上下文转场对象使用”from”和”to”术语来标识视图控制器, 视图,和转场中涉及到的frame
矩形。在转场开始的时候”from”视图控制器的视图总是在当前屏幕上,在转场结束的时候”to”视图控制器将会被看见。在图10-4中你可以看到,在presentation
和dismissal
时“from”和”to"视图控制器交换了位置。
交换值使它更容易写一个单个的动画来处理presentations
和dismissals
。当你设计你的动画时,你总是必须要做的是包含一个属性来知道它的动画是presentation
还是dismissal
。两者之间唯一需要的区别如下:
- 对于
presentation
, 添加”to”视图到容器视图的层级。 - 对于
dismissal
,从容器视图的层级移除”from”视图。
创建转场动画
在典型的presentation
期间,属于被呈现的视图控制器的视图被动画化到位。其它的视图可能作为你的presentation
的一部分动画,但是你的动画的主要目标总是被添加到视图层级中的视图。
当动画主要视图时,你配置你的动画的基本动作是相同的。你从转场上下文获取你需要的对象和数据,并使用这些信息来创建你的真实的动画。
-
Presentation
动画:- 使用
viewControllerForKey:
和viewForKey:
方法来获取涉及到转场中的视图控制器和视图。 - 设置”to”视图的开始位置。将任何其它属性设置为其初始值。
- 从转场上下文的
finalFrameForViewController:
获取”to”视图的最终位置。 - 把”to"视图作为子视图添加到容器视图中。
- 创建动画
- 在你的动画块中,将”to”视图动画到它在容器视图中的最终位置。将任何其它属性设置为其最终值。
- 在完成块中,调用
completeTransition:
方法,并执行其它的清理。
- 使用
-
Dismissal
动画:- 使用
viewControllerForKey:
和viewForKey:
方法来获取涉及到转场中的视图控制器和视图。 - 计算”from”视图的结束位置。属于被呈现的视图控制器的视图开始消失。
- 将”to"视图作为子视图添加到容器视图中。
- 在
presentation
期间,当转场完成时属于呈现的视图控制器的视图被移除。结果,你必须在dismissal
操作期间将那个视图添加回容器中。 - 创建动画
- 在你的动画块中,将”from”视图动画到它在容器视图中的最终位置。将任何其它属性设置为其最终值。
- 在完成块中,将”from”视图从你的视图层级中移除并调用
completeTransition:
方法。根据需要执行任何其它的清理。
- 使用
图10-5显示了一个自定义的presentation
和dismissal
转场,动画它的对角视图。在presentation
期间,被呈现的视图空屏幕开始并向对角线向左上方动画直到它是可见的。在dismissal
期间,视图反转方向,向右下方动画直到它再次离开屏幕。
列表10-2显示你将如何实现图10-5上显示的转场。在获取到动画需要的对象后,animateTransition:
方法计算被影响的视图的frame矩形。在presentation
期间,被呈现的视图由“toView”表示。在dismissal
期间,被消失的视图由”fromView”表示。presenting
属性是动画对象自己的自定义属性,在创建动画对象的时候将转场代理设置为合适的值。
//Listing 10-2Animations for implementing a diagonal presentation and dismissal
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// Get the set of relevant objects.
UIView *containerView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext
viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
// Set up some variables for the animation.
CGRect containerFrame = containerView.frame;
CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];
// Set up the animation parameters.
if (self.presenting) {
// Modify the frame of the presented view so that it starts
// offscreen at the lower-right corner of the container.
toViewStartFrame.origin.x = containerFrame.size.width;
toViewStartFrame.origin.y = containerFrame.size.height;
}
else {
// Modify the frame of the dismissed view so it ends in
// the lower-right corner of the container view.
fromViewFinalFrame = CGRectMake(containerFrame.size.width,
containerFrame.size.height,
toView.frame.size.width,
toView.frame.size.height);
}
// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;
// Animate using the animator's own duration value.
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
if (self.presenting) {
// Move the presented view into position.
[toView setFrame:toViewFinalFrame];
}
else {
// Move the dismissed view offscreen.
[fromView setFrame:fromViewFinalFrame];
}
}
completion:^(BOOL finished){
BOOL success = ![transitionContext transitionWasCancelled];
// After a failed presentation or successful dismissal, remove the view.
if ((self.presenting && !success) || (!self.presenting && success)) {
[toView removeFromSuperview];
}
// Notify UIKit that the transition has finished
[transitionContext completeTransition:success];
}];
}
动画结束后清理
在转场动画结束后,请务必调用completeTransition:
方法。调用这个方法告诉UIKit
转场完成了,用户可以开始使用被呈现的视图控制器。调用这个方法也会引发其它完成处理程序的级联,包括一个来自于presentViewController:animated:completion:
方法和动画对象自己的animationEnded:
方法。
调用completeTransition:
方法最好的地方是在你的动画块的完成处理方法里。
因为转场能够被取消,你应该使用上下文对象的transitionWasCancelled
方法的返回值来确定需要清理。当一个presentation
被取消时,你的动画必须撤销对视图层级所做的任何修改。一个成功的dismissal
需要相同的操作。
将交互式添加到你的转场
最容易的方法使你的动画交互是使用UIPercentDrivenInteractiveTransition
对象。UIPercentDrivenInteractiveTransition
对象与你现有的动画对象配合使用来控制其动画时间。它使用你提供的完成百分比值。你必须要做的是编写需要的事件处理的代码来计算完成百分比值并新的事件达到时更新它。
你可以使用带有子类或不带有子类的UIPercentDrivenInteractiveTransition
类。如果你子类化了,使用你的子类的init
方法(或startInteractiveTransition:
方法)来执行一次创建你的事件处理代码。之后,使用你的自定义的事件处理代码来计算新的完成百分比值并调用updateInteractiveTransition:
方法。当你的代码确定转场应该完成了,调用finishInteractiveTransition
方法。
列表10-3显示UIPrecentDrivenInteractiveTransition
子类的startInteractiveTransition:
方法的自定义实现。这个方法设置一个平移手势识别器来跟踪触摸事件并将该手势识别器安装在动画的容器视图上。它还保存转场上下文的引用以供以后使用。
Listing 10-3Configuring a percent-driven interactive animator
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// Always call super first.
[super startInteractiveTransition:transitionContext];
// Save the transition context for future reference.
self.contextData = transitionContext;
// Create a pan gesture recognizer to monitor events.
self.panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleSwipeUpdate:)];
self.panGesture.maximumNumberOfTouches = 1;
// Add the gesture recognizer to the container view.
UIView* container = [transitionContext containerView];
[container addGestureRecognizer:self.panGesture];
}
在任何新的事件达到时手势识别器会调用它的动作方法。你的动作方法的实现可以使用手势识别器的状态信息来确定手势是否成功,失败或一直在进行。同时,你可以使用最新的触摸事件信息来计算手势的新的百分比值。
列表10-4显示由列表10-3中配置的平移手势识别器调用的方法。当新的事件到达时,此方法使用垂直行程距离来计算动画的完成百分比值。当手势结束时,该方法结束转场。
// Listing 10-4Using events to update the animation progress
-(void)handleSwipeUpdate:(UIGestureRecognizer )gestureRecognizer {
UIView container = [self.contextData containerView];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
// Reset the translation value at the beginning of the gesture.
[self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
// Get the current translation value.
CGPoint translation = [self.panGesture translationInView:container];
// Compute how far the gesture has travelled vertically,
// relative to the height of the container view.
CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));
// Use the translation value to update the interactive animator.
[self updateInteractiveTransition:percentage];
}
else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
// Finish the transition and remove the gesture recognizer.
[self finishInteractiveTransition];
[[self.contextData containerView] removeGestureRecognizer:self.panGesture];
}
}
注意:你计算的值表示动画的整个长度的完成百分比。对于交互式动画,你可能想要避免效应,例如动画本身的初始速度、阻尼值和非线性完成曲线。这种效应倾向于将事件的触摸位置与任何下层视图的移动分离。
创建与转场一起运行的动画
涉及转场的视图控制器可以在任何presentation
或转场动画之上执行附加动画。例如,被呈现的视图控制器可能在转场期间动画它自己的视图层级,并在转场出现时添加移动效果或其它视觉反馈。任何对象能够创建动画,只要它能够访问被呈现或呈现的视图控制器的transitionCoordinator
属性。转场协调器属性只在转场正在进行时存在。
为了创建动画,调用转场协调器的animateAlongsideTransition:completion:
或animateAlongsideTransitionInView:animation:completion:
方法。这个你提供的块被存储直到转场动画开始,此时它们与其余的动画一起执行。
使用Presentation
控制器与你的动画
对于自定义presentation
,你可以提供你自己的presentation
控制器来给被呈现的视图控制器一个自定义的外观。Presentation
控制器管理着视图控制器和其内容分离的任何自定义的chrome.
例如,位于视图控制器视图的后面的调光视图将由presentation
控制器管理。事实上它没有管理一个特殊的视图控制器的视图意味着你可以在你的应用程序中使用相同的presentation
控制器与任何视图控制器。
你提供一个自定义的presentation
控制器来自于被呈现视图控制器的转场代理。(视图控制器的modalTransitionStyle
属性必须设置为UIModalPresentationCustom
) presentation
控制器与任何动画对象并行操作。由于动画对象将视图控制器的视图动画到位置,presentation
控制器会将任何附加视图动画到位置。转场结束时,presentation
控制器有机会对视图层级执行任何最终调整。
关于如何创建一个自定义的presentation
控制器的更多信息,请看Creating Custom Presentations
。