iOS自定义转场动画(push、pop动画)

iOS7推出了新的转场动画API,以协议id<UIViewControllerInterativeTransition>、id<UIViewAnimatedTransitioning>方式开放给开发者,不同于代理、类别,这样更易于我们自定义动画,更加灵活。下面介绍一下自定义转场动画

仿酷狗push
push
要使用的协议
  • UIViewControllerInteractiveTransitioning 交互协议,主要在右滑返回时用到
  • UIViewControllerAnimatedTransitioning 动画协议,含有动画时间及转场上下文两个必须实现协议
  • UIViewControllerContextTransitioning 动画协议里边的协议之一,动画实现的主要部分
  • UIPrecentDrivenInteractiveTransition 用在交互协议,百分比控制当前动画进度。
自定义步骤
  • 首先要实现navigation的代理,navigation有两个返回id类型的协议,实现这两个协议

第一个方法返回一个UIPercentDrivenInterativeTransition类型的对象即可,这个对象默认实现了UIPercentInterativeTransitioning协议,需要注意的是,这个返回值主要是用于交互动画,也就是右滑返回时需要用到,这里我在基类baseViewController定义了一个UIPercentDrivenInterativeTransition类型的属性。
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController )navigationController
interactionControllerForAnimationController:(WTKBaseAnimation
) animationControlle{
return animationControlle.interactivePopTransition;
}
第二个方法我自定义了一个遵循UIViewControllerAnimationTransitioning协议的类WTKBaseAnimation,
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(WTKBaseViewController *)fromVC
toViewController:(UIViewController *)toVC{
if (fromVC.interactivePopTransition)
{
WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
animation.interactivePopTransition = fromVC.interactivePopTransition;
return animation; //手势
}
else
{
WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
return animation;//非手势
};}

第二个方法返回对象自定义如下


baseAnimation构建方法.png

也就是需要把navigation的代理方法中的参数都传过来。
在本类中,需要实现转场动画协议:
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
return self.duration;}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
if (self.transitionType == UINavigationControllerOperationPush)
{
    [self push:transitionContext];
}
else if (self.transitionType == UINavigationControllerOperationPop)
{
    [self pop:transitionContext];
}}   

[self push:transitionContext]; [self pop:transitionContext];在本类中并没有真正的实现,具体交给子类实现

Paste_Image.png

- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext{}
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext{}

下面介绍子类的具体实现,

push

|
<pre><code>- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {

UIViewController * fromVc   = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

UIViewController * toVc     = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

NSTimeInterval duration     = [self transitionDuration:transitionContext];

CGRect bounds               = [[UIScreen mainScreen] bounds];

fromVc.view.hidden          = YES;

[[transitionContext containerView] addSubview:toVc.view];

[[toVc.navigationController.view superview] insertSubview:fromVc.snapshot belowSubview:toVc.navigationController.view];

toVc.navigationController.view.transform = 

CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0);

[UIView animateWithDuration:duration
                      delay:0
     usingSpringWithDamping:1.0
      initialSpringVelocity:0
                    options:UIViewAnimationOptionCurveLinear
                 animations:^{
                     fromVc.snapshot.transform = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);
                     toVc.navigationController.view.transform = CGAffineTransformMakeTranslation(0, 0);
                 }
                 completion:^(BOOL finished) {
                     fromVc.view.hidden = NO;
                     [fromVc.snapshot removeFromSuperview];
                     [transitionContext completeTransition:YES];
}];
}

</code></pre>
其中fromVC与toVC、为函数参数transitionContext协议获得,duration调用父类方法获得,最终为navigation的代理方法中返回的时间,也可以自定义。fromVC为原来的ViewController,toVC为要push的VC
首先将fromVC的view隐藏,使用VC的snapshot代替,snapshot为viewController的截图,这里使用类别关联属性实现的。
[transitionContext containerView] 为容器,存转场需要的view,
分别将toVC.view及fromVC.snapshot添加到容器中,注意添加顺序、view存放的顺序。

下面将toVC移动到屏幕右边,这里使用的是改变transform,

使用UIView做动画,需要注意的是,使用usingSpringWithDamping的动画,关于这个动画不再多说。
UIView动画,需要把fromVC移动到左边(移动多少可自定),toVC移动到右边。
动画完成后,需要把原来隐藏的fromVC.view显示,添加到容器的view移除,当前显示的vc不需要移除。
** 最后需要调用转场完成方法** [transitionContext completeTransition:YES];

Pop

|

  - (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {

WTKBaseViewController * fromVc  = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

UIViewController * toVc         = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

NSTimeInterval duration         = [self transitionDuration:transitionContext];

CGRect bounds                   = [[UIScreen mainScreen] bounds];

[fromVc.view addSubview:fromVc.snapshot];
fromVc.navigationController.navigationBar.hidden = YES;
fromVc.view.transform = CGAffineTransformIdentity;

toVc.view.hidden                = YES;
toVc.snapshot.transform         = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);

[[transitionContext containerView] addSubview:toVc.view];
[[transitionContext containerView] addSubview:toVc.snapshot];
[[transitionContext containerView] sendSubviewToBack:toVc.snapshot];

if (fromVc.interactivePopTransition)
{
    [UIView animateWithDuration:duration
                          delay:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                         toVc.snapshot.transform = CGAffineTransformIdentity;
                     }
                     completion:^(BOOL finished) {

                         toVc.navigationController.navigationBar.hidden = NO;
                         toVc.view.hidden = NO;

                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         fromVc.snapshot = nil;

                         if (![transitionContext transitionWasCancelled]) {
                             toVc.snapshot = nil;
                         }

                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

}
else
{
    [UIView animateWithDuration:duration
                          delay:0
         usingSpringWithDamping:1
          initialSpringVelocity:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                         toVc.snapshot.transform = CGAffineTransformIdentity;
                     }
                     completion:^(BOOL finished) {

                         toVc.navigationController.navigationBar.hidden = NO;
                         toVc.view.hidden = NO;

                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         fromVc.snapshot = nil;

                         if (![transitionContext transitionWasCancelled]) {
                             toVc.snapshot = nil;
                         }

                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}}

pop方法与push类似,不再多说,需要注意的是** 一定要区分手势和非手势 **,也就是如果点击按钮返回,需要使用usingSpringWithDamping动画,右滑返回不使用这个。
判断方式fromVc.interactivePopTransition,这个为在基类baseViewController里边自定义的一个UIPercentDrivenInterativeTransition类型的属性,也就是navigation代理方法- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(WTKBaseAnimation*) animationControlle的返回值。

  • 在baseViewController中添加手势:UIPanGestureRecognizer,添加到self.view上面,viewDidLoad中如下:

if (self.navigationController && self != self.navigationController.viewControllers.firstObject)
{
    UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePopRecognizer:)];
    [self.view addGestureRecognizer:popRecognizer];
    popRecognizer.delegate = self;
}

在手势方法中创建UIPercentInterativeTransition,在拖动过程中,用这个实例变量调用updateInteractiveTransition方法,代码如下


- (void)handlePopRecognizer:(UIPanGestureRecognizer *)recognizer{
CGFloat progress = [recognizer translationInView:self.view].x / CGRectGetWidth(self.view.frame);
progress = MIN(1.0, MAX(0.0, progress));
NSLog(@"progress---%.2f",progress);
if (recognizer.state == UIGestureRecognizerStateBegan)
{
    self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc]init];
    [self.navigationController popViewControllerAnimated:YES];
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
    [self.interactivePopTransition updateInteractiveTransition:progress];
}
else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
{
    if (progress > 0.25)
    {
        [self.interactivePopTransition finishInteractiveTransition];
    }
    else
    {
        [self.interactivePopTransition cancelInteractiveTransition];
    }
    self.interactivePopTransition = nil;
}}

上面定义的progress,为了记录滑动的百分比,随时更新interactivePopTransition 当手势结束,根据progress判断当前是否可以pop回来,这里是以0.25为标准。

代码连接 git连接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上...
    伯恩的遗产阅读 53,842评论 37 381
  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,360评论 3 44
  • iOS7.0后苹果提供了自定义转场动画的API,利用这些API我们可以改变 push和pop(navigation...
    薛定喵的鹅阅读 17,746评论 1 37
  • 深爱过的人真的没法做朋友。 直到当我彻底放下的时候,我才试着开始相信也许你真的不是那个会陪我走完一生的人。 再见。
    抱抱胖胖阅读 245评论 0 0
  • 背景 在阅读这篇文章之前,我们先来看看下面这个webpack的配置文件,如果�在此之前你已经在熟练使用webpac...
    淡漠_微雨阅读 492评论 0 0