在iOS中Navigation Controller推入和推出VC,弹出Modal VC或者是Tab Controller切换选中tab时都会有默认的动画。统一固然是好的,但有时候我们还是想要有点新意,然而要去改变这些默认的效果还是比较麻烦。但是自从有了转场动画之后,生活瞬间就变得美好起来了。
转场动画在iOS7中引入,可以分为两类:非交互式和交互式。顾名思义,二者的区别就在于用户是不是可以干预动画的执行进度。接下来,让我们先暂时忘掉这两个概念。
总的来说,转场动画由两个部件组成:Animation Controller和Animation Delegate。它们通过以下方式结合在一起:
系统:hey, Delegate,我要做页面切换了,需要自定义动画吗?
Delegate:要啊,来,我给你个动画控制器,你就照着这个做吧。
系统:......,好吧(原本以为我是大爷的)。
系统默默地去执行动画控制器定制的动画了。
实现动画控制器时需要实现UIViewControllerAnimatedTransitioning协议,一般我们需要实现的方法为:
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext;
:告诉系统自定义动画持续的时间,系统通过这个时间来保证页面切换动画和页面切换动画发生时同时发生的动画保持一致。比如,push页面的时候,导航栏的变化。
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
:提供自定义的动画实现。
在上面的接口中都出现了transitionContext这个参数,它由系统负责初始化,用于描述转场动画的上下文信息。它实现了UIViewControllerContextTransitioning协议,通过它我们可以得到如from/toView[Controller], containerView等信息。
Delegate分为两类,一个是为Navigation时push/pop提供的UINavigationControllerDelegate,另一个是为其他场景(如显示Modal Controller, Tab切换等)提供的UIViewControllerTransitioningDelegate。如上面对话所描述的,Delegate的作用就是告诉系统应该使用什么动画控制器,这两个Delegate也不例外,只是具体方法略有差异。
- UINavigationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
operation会告诉我们是Push还是Pop
- UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
为页面呈现和消失分别定义了一个方法。
了解上述内容后,要使用转场动画就很简单了。首先我们需要定义一个动画控制器,然后我们还需要定义一个Delegate来告诉系统使用我们的控制器。再然后就没有然后了,一切就交给系统去完成吧。下面来看个具体的例子。
定义动画控制器
我为Push和Pop分别定义了动画控制器,大致实现接近,这里只看下Push的实现:
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return TRANSITION_DURATION;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toView];
toView.frame = containerView.bounds;
CATransform3D scaleTransform = CATransform3DMakeScale(0.6f, 0.6f, 0.6f);
CATransform3D translationR = CATransform3DMakeTranslation(containerView.frame.size.width, 0, 0);
CATransform3D translationL = CATransform3DMakeTranslation(-containerView.frame.size.width, 0, 0);
toView.layer.transform = translationR;
[UIView animateWithDuration:TRANSITION_DURATION
animations:^{
toView.layer.transform = CATransform3DIdentity;
fromView.layer.transform = CATransform3DConcat(translationL, scaleTransform);
}
completion:^(BOOL finished) {
fromView.layer.transform = CATransform3DIdentity;
BOOL cancelled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:!cancelled];
}];
}
定义Delegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC{
if(operation == UINavigationControllerOperationPush){
return self.pushAnimationController;
}
else if(operation == UINavigationControllerOperationPop){
return self.popAnimationController;
}
return nil;
}
之后将NavigationController的delegate设成我们的Delegate就大功告成了,so easy!
有了前面的基础,我们就已经可以实现很多炫酷的效果了。不过在前面的示例中在用户按下按钮后,动画就开始执行,用户不能干预动画的执行过程。按照一开始讨论的分类来看,它应该属于非交互式的。如果我们需要实现类似系统按住屏幕左边缘可以让页面跟随手指移动的效果,就需要交互式的转场动画了。
不过别担心,别被这些概念唬住,交互式动画其实就是修饰我们之前定义的动画。比如系统有个UIPercentDrivenInteractiveTransition交互式动画实现,它的作用就是根据用户的行为控制动画的进度。
要告诉系统要使用交互式动画,在之前的两个Delegate上都有对应的方法:
//navigation delegate
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
//view controller delegate
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
接下来,我们就来使用UIPercentDrivenInteractiveTransition跟随手指的效果。
Delegate
只需要简单地返回一个交互式动画控制器,如下:
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController{
return self.isInteractiveMode ? self.percentTransition : nil;
}
处理手势操作
- (void)handlePanGR:(UIScreenEdgePanGestureRecognizer *)panGR{
switch (panGR.state) {
case UIGestureRecognizerStateBegan:
{
//拖动开始,进入交互模式
[self.naviDelegate enterInteractiveMode];
[self.navigationController popViewControllerAnimated:YES];
}
break;
case UIGestureRecognizerStateChanged:
{
//根据拖动距离更新交互式动画的进度
CGPoint pt = [panGR translationInView:panGR.view];
[self.naviDelegate.percentTransition updateInteractiveTransition:pt.x / panGR.view.frame.size.width];
}
break;
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateCancelled:
{
//拖动手势取消或者失败,离开交互模式,并通知交互式动画控制器取消动画
[self.naviDelegate leaveInteractiveMode];
[self.naviDelegate.percentTransition cancelInteractiveTransition];
}
break;
case UIGestureRecognizerStateEnded:
{
//拖动结束,离开交互模式,并根据移动距离是否要完成动画
[self.naviDelegate leaveInteractiveMode];
CGPoint pt = [panGR translationInView:panGR.view];
CGFloat delta = 0.0000001;
if(pt.x - panGR.view.frame.size.width / 2 <= delta){
[self.naviDelegate.percentTransition cancelInteractiveTransition];
}
else{
[self.naviDelegate.percentTransition finishInteractiveTransition];
}
}
break;
default:
break;
}
}
转场动画的基本内容就介绍完了,除了如何实现动画本身,还应该借鉴苹果的设计方式,职责分离,各自负责各自的事情,减少依赖,便于扩展。
欢迎大家吐槽。
完整Demo