专栏首页一“技”之长玩转iOS转场动画 原

玩转iOS转场动画 原

玩转iOS转场动画

一、引言

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

iOS动画开发之一——UIViewAnimation动画的使用:https://my.oschina.net/u/2340880/blog/484457

iOS动画开发之二——UIView动画执行的另一种方式:https://my.oschina.net/u/2340880/blog/484538

iOS动画开发之三——UIView的转场切换:https://my.oschina.net/u/2340880/blog/484669

iOS动画开发之四——核心动画编程(CoreAnimation):https://my.oschina.net/u/2340880/blog/484793

iOS动画开发之五——炫酷的粒子效果:https://my.oschina.net/u/2340880/blog/485095

iOS开发CoreAnimation解读之一——初识CoreAnimation核心动画编程:https://my.oschina.net/u/2340880/blog/535235

iOS开发CoreAnimation解读之二——对CALayer的分析:https://my.oschina.net/u/2340880/blog/536048

iOS开发CoreAnimation解读之三——几种常用Layer的使用解析:https://my.oschina.net/u/2340880/blog/538024

iOS开发CoreAnimation解读之四——Layer层动画内容:https://my.oschina.net/u/2340880/blog/539599

iOS开发CoreAnimation解读之五——高级动画技巧:https://my.oschina.net/u/2340880/blog/539827

iOS开发CoreAnimation解读之五——CATransform3D变换的应用:https://my.oschina.net/u/2340880/blog/539878

iOS中播放gif动态图的方式探讨:https://my.oschina.net/u/2340880/blog/608560

iOS界面布局之三——纯代码的autoLayout及布局动画:https://my.oschina.net/u/2340880/blog/524089

开始本篇博客前,先上一张图,如果你觉得不好理解,没关系,看完后面的内容再回来看这张图,就一目了然了。

二、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;

其实现原理与我们上面进行完全的自定义是一样的。

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

    导航转场动画的原理与模态跳转转场动画的原理基本是一致的,不同的我们需要设置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

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

四、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{
    
}

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • iOS开发CoreAnimation解读之五——高级动画技巧

            CoreAnimation中还有一个非常重要的类:CATransaction事物类,这个可以同时设置多个layer层的动画效果。可以通过隐式和显...

    珲少
  • iOS开发CoreAnimation解读之四——Layer层动画内容

            通过前几篇博客的介绍,我们可以了解到layer层可以设置许多与控件UI相关的属性,并且对于iOS开发,UIView层的属性是会映射到CALaye...

    珲少
  • AppleWatch开发入门九——Watch帧动画的实现

            动画一直是iOS系统的一大亮点,CoreAnimation和粒子效果的支持,开发者可以很容易的做出效果炫酷的动画特效。在watchOS中,由于性...

    珲少
  • CAAnimation 核心动画概念一、 CAAnimation二、 CAPropertyAnimation三、CABasicAnimation(基本动画)CAPropertyAnimation的子类

    用户2141756
  • 【案例】 如何用HTML5 制作3D图片阴影翻转动画

    HTML5非常强大,尤其是和CSS3结合,有时候能达到非同凡响的网页动画效果。这次给同学们带来的是一款基纯HTML5实现的3D图片阴影翻转动画

    用户1730674
  • 深度学习在文本分类中的应用

    近期阅读了一些深度学习在文本分类中的应用相关论文(论文笔记),同时也参加了CCF 大数据与计算智能大赛(BDCI)2017的一个文本分类问题的比赛:让AI当法...

    llhthinker
  • 深度学习在文本分类中的应用

    近期阅读了一些深度学习在文本分类中的应用相关论文(论文笔记:http://t.cn/RHea2Rs ),同时也参加了 CCF 大数据与计算智能大赛(BDCI)2...

    AI研习社
  • 教师听课评课系统设计

    需求分析:管理教师安排任课教师课程,安排听课教师,听课教师到教室听课,使用手机记录学生表现、教师表现、综合表现、以及建议,告别纸质化。课后授课教师查看所有听课人...

    热心的程序员
  • 使用python播放视频文件

    py3study
  • 前后端分离了,然后呢?

      前言   前后端分离已经是业界所共识的一种开发/部署模式了。所谓的前后端分离,并不是传统行业中的按部门划分,一部分人纯做前端(HTML/CSS/JavaSc...

    逸鹏

扫码关注云+社区

领取腾讯云代金券