文档章节

玩转iOS转场动画

珲少
 珲少
发布于 2018/07/04 00:12
字数 2884
阅读 1873
收藏 5

玩转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{
    
}

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

 

© 著作权归作者所有

珲少

珲少

粉丝 886
博文 394
码字总数 475753
作品 0
上海
程序员
私信 提问
加载中

评论(1)

大数据工程师阿华
大数据工程师阿华
iOS安全攻防,AR技术,ARKit技术,移动架构,支付宝,底层,高级进阶等,逆向,音视频处理技术,新技术开发,OpenGL ES,人工智能,进阶,底层夸平台讲解讲解,都是纯干货分享,需要这方面的视频资料可以加我QQ群319819749适合1-6开发,欢迎加入群内不定期分享干货,最新面试资料哦
系统学习iOS动画之四:视图控制器的转场动画

这系列问文章图片比较多,特别是gif图,简书的图片上传老出问题,我已奔溃😒🤦♀️🤦♀️!! 直接到我的博客看吧, 传送门🚪:系统学习iOS动画之四:视图控制器的转场动画 之前学习了视...

Andy_Ron
2018/12/22
0
0
iOS UIView Block 动画- (基础动画, 关键帧动画, 动画组)

UIView本身对于基本动画和关键帧动画、转场动画都有相应的封装,在对动画细节没有特殊要求的情况下使用起来也要简单的多 1、UIView Block 基础动画 ,转场动画+ 缩放 2、关键帧动画(里面加上...

朝雨晚风
2018/05/31
0
0
iOS开发UI篇—核心动画(转场动画和组动画

iOS开发UI篇—核心动画(转场动画和组动画) 一、转场动画简单介绍 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点 UINa...

caoxiang
2015/08/18
72
0
iOS 全屏侧滑手势/UIScrollView/UISlider间滑动手势冲突

一、前期准备 有一个支持全屏侧滑返回的视图控制器ViewController,ViewController.view上有一个UIScrollView,UIScrollView上有UISlider。俺直接在之前的示例Demo上演示,简书地址:iOS 自定...

且行且珍惜_iOS
2018/08/17
0
0
系统学习iOS动画之零:说明和目录

动画制作很有趣,可以为用户界面注入活力。 如果使用得当,动画可以向用户传达信息,并将用户注意力吸引到界面的重要部分。 之前也做过一些iOS动画,但一直没有系统学习过,这次我用RW网站的...

Andy_Ron
2018/12/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Activity启动模式二

上篇文章Activity启动模式一主要介绍了Activity的四种启动模式,这些启动模式都是在AndroidManifest中进行配置的。除此之外,Android系统还通过Intent类提供了一些标志位,同样可以指定Activ...

ltlovezh
49分钟前
7
0
三原色还原

1、Color Filter Array — CFA 随着数码相机、手机的普及,CCD/CMOS 图像传感器近年来得到广泛的关注和应用。 图像传感器一般都采用一定的模式来采集图像数据,常用的有 BGR 模式和 CFA 模式...

天王盖地虎626
今天
7
0
kubernetes pod exec接口调用

正文 一般生产环境上由于网络安全策略,大多数端口是不能为集群外部访问的。多个集群之间一般都是通过k8s的ApiServer组件提供的接口通信,如https://192.168.1.101:6443。所以在做云平台时,...

码农实战
今天
8
0
3_数组

3_数组

行者终成事
今天
8
0
经典系统设计面试题解析:如何设计TinyURL(二)

原文链接:https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR 编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助...

APEMESH
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部