玩转iOS转场动画

玩转iOS转场动画

1、引言

    关于动画在iOS开发中的应用,曾经整理过一系列的博客进行总结。包括简单的UIView层的动画,CALayer层的动画,Autolayout自动布局动画以及CoreAnimation核心动画框架等。本篇博客主要深刻讨论视图控制器、导航控制器来进行界面跳转时的专场动画相关内容。以前的动画相关博客列举以下:编程

iOS动画开发之一——UIViewAnimation动画的使用:http://www.javashuo.com/article/p-mhgdzmkw-hy.html框架

iOS动画开发之二——UIView动画执行的另外一种方式:http://www.javashuo.com/article/p-tmehfbif-es.html函数

iOS动画开发之三——UIView的转场切换:http://www.javashuo.com/article/p-ymkfcyhc-gw.html布局

iOS动画开发之四——核心动画编程(CoreAnimation):http://www.javashuo.com/article/p-ailnsekr-hx.html动画

iOS动画开发之五——炫酷的粒子效果:http://www.javashuo.com/article/p-pecotnsr-cx.htmlatom

iOS开发CoreAnimation解读之一——初识CoreAnimation核心动画编程:http://www.javashuo.com/article/p-yawoejry-bw.htmlurl

iOS开发CoreAnimation解读之二——对CALayer的分析:http://www.javashuo.com/article/p-vfwuxjxq-t.htmlspa

iOS开发CoreAnimation解读之三——几种经常使用Layer的使用解析:http://www.javashuo.com/article/p-cszuhugx-bg.html.net

iOS开发CoreAnimation解读之四——Layer层动画内容:http://www.javashuo.com/article/p-tbridkqp-bp.html设计

iOS开发CoreAnimation解读之五——高级动画技巧:http://www.javashuo.com/article/p-oogudvca-ce.html

iOS开发CoreAnimation解读之五——CATransform3D变换的应用:http://www.javashuo.com/article/p-aslceclu-v.html

iOS中播放gif动态图的方式探讨:http://www.javashuo.com/article/p-rxievqbk-q.html

iOS界面布局之三——纯代码的autoLayout及布局动画:http://www.javashuo.com/article/p-takuywzr-dx.html

开始本篇博客前,先上一张图,若是你以为很差理解,不要紧,看完后面的内容再回来看这张图,就一目了然了。

2、UIViewController进行模态跳转的转场

    首先,使用CoreAnimation框架中的CATransition类也能够实现视图控制器的转场动画,前面的博客有过讨论,这里再也不重复。presentViewController这个函数使用率可谓是很是高的,默认的转场动画为新的视图控制器从下向上弹出,dismissViewControllerAnimated函数的返回动画则是弹出动画的逆序播放。其实,系统提供了4种转场动画供开发者选择,经过设置将要弹出的UIViewController实例的以下属性:

@property(nonatomic,assign) UIModalTransitionStyle modalTransitionStyle;

UIModalTransitionStyle是一个枚举,以下:

typedef NS_ENUM(NSInteger, UIModalTransitionStyle) {
    UIModalTransitionStyleCoverVertical = 0,  //从下向上弹起  默认项
    UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED, //水平翻转
    UIModalTransitionStyleCrossDissolve,    //渐隐渐现
    UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, //翻页
};

不少时候,上面4种枚举的转场动画样式并不能知足咱们的需求,咱们可使用UIViewControllerTransitioningDelegate协议来彻底自定义想要的转场动画效果。

    首先建立一个类,使其遵照UIViewControllerTransitioningDelegate协议,好比我这里将类名去作TransDelegate,继承自NSObject。在界面跳转时,将要弹出的视图控制器设置以下:

ViewController2 * v2 = [ViewController2 new];
self.transDelegate = [[TransDelegate alloc]init];
v2.transitioningDelegate = self.transDelegate;
[self presentViewController:v2 animated:YES completion:nil];

咱们先来看UIViewControllerTransitioningDelegate协议中的以下几个函数:

//这个函数用来设置当执行present方法时 进行的转场动画
/*
presented为要弹出的Controller
presenting为当前的Controller
source为源Contrller 对于present动做  presenting与source是同样的
*/
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
//这个函数用来设置当执行dismiss方法时 进行的转场动画
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

//这个函数用来设置当执行present方法时 进行可交互的转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
//这个函数用来设置当执行dismiss方法时 进行可交互的转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
//iOS8后提供的新接口  返回UIPresentationController处理转场
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0);

咱们先来看上面的前两个函数,这两个函数都要返回一个实现了UIViewControllerAnimatedTransitioning协议的对象,UIViewControllerAnimatedTransitioning则用来负责具体的动画展现,例如咱们在建立一个命名为AniObject的类,继承自NSObject,使其实现UIViewControllerAnimatedTransitioning协议,在TransDelegate类中实现以下:

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
    return [AniObject new];
}

下面咱们来实现AniObject类来具体的处理动画效果:

UIViewControllerAnimatedTransitioning协议中有两个函数是必须实现的,以下:

//这个函数用来设置动画执行的时长
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
    return 2;
}
//这个函数用来处理具体的动画
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    //跳转的界面
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //最终的位置
    CGRect finalRect = [transitionContext finalFrameForViewController:toVC];
    //起始位置
    toVC.view.frame = CGRectOffset(finalRect, [[UIScreen mainScreen]bounds].size.width, 0);
    //添加到内容视图
    [[transitionContext containerView]addSubview:toVC.view];
    //执行动画
    [UIView animateWithDuration:[self transitionDuration:transitionContext]  animations:^{
        toVC.view.frame = finalRect;
    } completion:^(BOOL finished) {
        //完成动画
        [transitionContext completeTransition:YES];
    }];
}

上面咱们实现了一个简单的自定义转场动画,将present动画修改为了从右侧滑入,可是dismiss动画依然是默认的从下方划出。效果以下:

下面咱们来分析下transitionContext这个对象,这个对象其实是一个转场上下文,使用它来进行动画的定义和执行:

//容器视图 用来表现动画
@property(nonatomic, readonly) UIView *containerView;
//下面是几个只读属性
//是否应该执行动画
@property(nonatomic, readonly, getter=isAnimated) BOOL animated;
//是不是可交互的
@property(nonatomic, readonly, getter=isInteractive) BOOL interactive; // This indicates whether the transition is currently interactive.
//是否被取消了
@property(nonatomic, readonly) BOOL transitionWasCancelled;
//转场风格
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;
//调用这个函数来更新转场过程的百分比 用于可交互动画的阈值
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//完成可交互的转场交互动做时调用
- (void)finishInteractiveTransition;
//取消可交互的转场交互动做时调用
- (void)cancelInteractiveTransition;
//转场动画被中断  暂停时调用
- (void)pauseInteractiveTransition;
//转场动画完成时调用
- (void)completeTransition:(BOOL)didComplete;
//获取转场中的两个视图控制器
/*
UITransitionContextViewControllerKey的定义
UITransitionContextFromViewControllerKey  //原视图控制器
UITransitionContextToViewControllerKey    //跳转的视图控制器
*/
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;
//直接获取转场中的视图
/*
UITransitionContextFromViewKey  //原视图
UITransitionContextToViewKey    //转场的视图
*/
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key;
//获取视图控制器的初识位置
- (CGRect)initialFrameForViewController:(UIViewController *)vc;
//获取视图控制器转场后的位置
- (CGRect)finalFrameForViewController:(UIViewController *)vc;

经过上面的介绍,咱们可使用UIViewControllerContextTransitioning为所欲为的定制转场动画,可是还有一个困难咱们没法克服,那就是能够交互的转场动画。咱们在使用系统的导航控制器时,右划返回效果对用户体验十分友好,咱们下面就来试着将视图控制器的模态跳转设计成相似导航可交互的。

    首先咱们须要实现TransDelegate类中的以下两个函数:

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
    return [AniObject new];
}
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{
    //遵照了UIViewControllerInteractiveTransitioning协议的对象
    return self.object;
}

UIViewControllerInteractiveTransitioning协议用来处理可交互的转场动画的具体表现,须要注意,由于使用的是可交互的转场动画,UIViewControllerAnimatedTransitioning协议中的animateTransition:方法能够空实现。下面咱们再建立一个遵照UIViewControllerInteractiveTransitioning协议的类,好比命名为IntObject,上面代码中的self.object便是这个类的示例,IntObject.h文件以下:

@interface IntObject : NSObject<UIViewControllerInteractiveTransitioning>

-(void)updateAniProgress:(CGFloat)progress;

-(void)finish;

-(void)cancel;

@end

IntObject.m文件实现以下:

@interface IntObject()

@property(nonatomic,strong)id<UIViewControllerContextTransitioning> context;

@end

@implementation IntObject

//这个函数用来保存transitionContext
-(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.context = transitionContext;
    //跳转的界面
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    //最终的位置
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    //添加到内容视图
    [[transitionContext containerView]insertSubview:toVC.view belowSubview:fromVC.view];
}
//更新动画状态
-(void)updateAniProgress:(CGFloat)progress{
    UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
    //最终的位置
    CGRect fR = CGRectMake( [UIScreen mainScreen].bounds.size.width*progress, 0, [UIScreen mainScreen].bounds.size.width,  [UIScreen mainScreen].bounds.size.height);
    frameVC.frame = fR;
}
//结束转场
-(void)finish{
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        frameVC.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    } completion:^(BOOL finished) {
        [self.context completeTransition:YES];
    }];
}
//取消转场
-(void)cancel{
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        frameVC.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    } completion:^(BOOL finished) {
        [self.context cancelInteractiveTransition];
    }];
}

@end

下面咱们来添加手势,在ViewController2类中添加以下代码:

@interface ViewController2 ()
@property(nonatomic,strong)UIPanGestureRecognizer * pan;
@end

@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    [self.view addGestureRecognizer:self.pan];
    // Do any additional setup after loading the view.
}


-(void)pan:(UIPanGestureRecognizer *)pan{

    CGPoint translatedPoint = [pan translationInView:self.view];
    CGFloat persent =  translatedPoint.x /  [[UIScreen mainScreen]bounds].size.width;
    if (persent<0) {
        return;
    }
    persent = fabs(persent);
    IntObject * obj = [(TransDelegate *)self.transitioningDelegate object];
    switch (pan.state) {
        case UIGestureRecognizerStateBegan:{
            [self dismissViewControllerAnimated:YES completion:nil];
            break;
        }
        case UIGestureRecognizerStateChanged:{
            //手势过程当中,经过updateInteractiveTransition设置pop过程进行的百分比
            [obj updateAniProgress:persent];
            break;
        }
        case UIGestureRecognizerStateEnded:{
            //手势完成后结束标记而且判断移动距离是否过半,过则finishInteractiveTransition完成转场操做,否者取消转场操做
            if (persent > 0.5) {
                [obj finish];
            }else{
                [obj cancel];
            }
            break;
        }
        default:
            break;
    }
}
-(UIPanGestureRecognizer *)pan{
    if (!_pan) {
        _pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    }
    return _pan;
}
@end

手势效果以下:

其实,上面演示的是咱们本身建立了一个类来实现UIViewControllerInteractiveTransitioning协议,其实系统也为咱们提供一个类:UIPercentDrivenInteractiveTransition类,咱们能够直接调用这个类的以下3个函数而不须要咱们本身重写了,可是必须实现UIViewControllerAnimatedTransitioning协议中的transitionContext函数来实现动画效果。

- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
- (void)finishInteractiveTransition;

其实现原理与咱们上面进行彻底的自定义是同样的。

3、导航转场动画的自定义

    导航转场动画的原理与模态跳转转场动画的原理基本是一致的,不一样的咱们须要设置UINavigationController实例的delegate为遵照UINavigationControllerDelegate协议的类对象。以后实现以下两个函数:

//设置转场的动画不管是push或pop 返回nil 则使用系统默认的导航转场动画
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC  {
    NSLog(@"sss");
    return nil;
}
//设置可交互的转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                   interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController{
    NSLog(@"aaa");
    return nil;
}

能够看到 animationControllerForOperation:函数依然须要返回一个遵照了UIViewControllerAnimatedTransitioning协议的对象,使用方式和前面所介绍的模态跳转自定义转场如出一辙。UINavigationControllerOperation这个枚举将告知开发者导航所作的操做,以下:

typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {
    UINavigationControllerOperationNone, //无
    UINavigationControllerOperationPush, //push操做
    UINavigationControllerOperationPop,  //pop操做
};

实现UIViewControllerInteractiveTransitioning协议以下:

@interface IntObject()

@property(nonatomic,strong)id<UIViewControllerContextTransitioning> context;

@end

@implementation IntObject


-(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.context = transitionContext;
    //跳转的界面
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    //最终的位置
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    //添加到内容视图
    [[transitionContext containerView]insertSubview:toVC.view belowSubview:fromVC.view];
}

-(void)updateAniProgress:(CGFloat)progress{
    UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
    //最终的位置
    CGRect fR = CGRectMake( [UIScreen mainScreen].bounds.size.width*progress, 0, [UIScreen mainScreen].bounds.size.width,  [UIScreen mainScreen].bounds.size.height);
    frameVC.frame = fR;
    [self.context updateInteractiveTransition:progress];
}

-(void)finish{
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        frameVC.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    } completion:^(BOOL finished) {
      [self.context finishInteractiveTransition];
        [self.context completeTransition:YES];
    }];
}

-(void)cancel{
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        frameVC.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    } completion:^(BOOL finished) {
        [self.context cancelInteractiveTransition];
        [self.context completeTransition:NO];
    }];
}

@end

如此便可以轻松实现可交互的自定义导航动画。

4、UITabBarController的转场动画

    UITabbar也能够进行转场动画的自定义,须要设置UITabBarController的delegate并实现协议中的以下两个函数:

//设置非交互的转场动画
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
                     animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                       toViewController:(UIViewController *)toVC  {
}
//设置交互的转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                               interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController{
    
}

这两个函数的应用和导航自定义动画基本是一致的,这里就再也不列举代码,简单的效果见下图:

相关文章
相关标签/搜索