美文网首页iOS Developer - AnimationiOS动画iOS Developer
OC-VC转场动画教学、动画知识浅谈

OC-VC转场动画教学、动画知识浅谈

作者: Stark_Dylan | 来源:发表于2016-03-30 13:56 被阅读736次

    标题党,动画知识只是在写转场动画的时候顺便提一下。不敢说很棒,但是不可缺少的知识点全都有。

    现在,转场动画的文章有好多,但大部分都是在给鱼而不是给渔,所以,今天恰好看到一些相关的知识顺便给大家献丑。 包教会!

    首先看一下视图出现的代码, 模态与推出:

    [self.navigationController pushViewController:[UIViewController new] animated:YES];
    [self presentViewController:[UIViewController new] animated:YES completion:nil];
    

    当你想使用视图转场动画的时候, 首先要明确你是用模态还是推出。然后重载方法。

    • 推出(这方法名的确符合OC风格-。-)
    - (nullable id <UIViewControllerInteractiveTransitioning>) 
    navigationController:(UINavigationController *)navigationController 
    interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController;
     
    - (nullable id <UIViewControllerAnimatedTransitioning>) 
    navigationController:(UINavigationController *)navigationController 
    animationControllerForOperation:(UINavigationControllerOperation)operation 
    fromViewController:(UIViewController *)fromVC 
    toViewController:(UIViewController *)toVC;
    
    • 模态
    - (nullable id <UIViewControllerAnimatedTransitioning>) 
    animationControllerForPresentedController: (UIViewController *)presented 
    presentingController:(UIViewController *)presenting 
    sourceController: (UIViewController *)source;
     
    - (nullable id <UIViewControllerAnimatedTransitioning>) 
    animationControllerForDismissedController: (UIViewController *)dismissed;
     
    - (nullable id <UIViewControllerInteractiveTransitioning>) 
    interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
     
    - (nullable id <UIViewControllerInteractiveTransitioning>)
    interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>) animator;
    

    二种转场中出现了UIViewControllerInteractiveTransitioningUIViewControllerAnimatedTransitioning从字面意思我们可以看出来,前者是负责交互的, 后者是负责动画的。 所以我们要改变转场动画, 要自己去实现UIViewControllerAnimatedTransitioning <这是非交互的转场,指的是完全按照系统指定的切换机制,用户无法中途取消或者控制进度切换>, 实现有交互的转场就要用UIViewControllerInteractiveTransitioning来搞了。 我们先来说AnimatedTrans。

    UIViewControllerAnimatedTransitioning

    先来看下这个协议中有什么方法:

    @protocol UIViewControllerAnimatedTransitioning <NSObject>
    
    // This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
    // synchronize with the main animation. 
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
    // This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
    
    @optional
    
    // This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
    - (void)animationEnded:(BOOL) transitionCompleted;
    
    @end
    

    二个必须实现的方法, 还有一个可选的, 报告动作结束的。 为了保证我们在VC中代码的高度可读性,我们创建一个新的类,实现这个协议中的方法,然后返回这个类的对象。(由于NC, VC只是重载的方法不同,下边就以VC为例说明)。

    • 创建 AnimatedVC 继承于 NSObject并实现协议
    
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface AnimatedVC : NSObject<UIViewControllerAnimatedTransitioning>
    
    @end
    
    • 在.M中实现方法(下边的说明我在代码注释中写明)
    //
    //  AnimatedVC.m
    //  Created by Dylan on 16/3/29.
    //
    
    #import "AnimatedVC.h"
    
    @interface AnimatedVC ()
    
    @property ( nonatomic, strong ) id <UIViewControllerContextTransitioning> transitionContext;
    
    @end
    
    @implementation AnimatedVC
    
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        
        // 动画的时间 duration
        return .3;
    }
    
    // This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    
        self.transitionContext = transitionContext;
        // 动画 开始
        
        // [step.1] 获取到当前VC 目标VC UITransitionContextFromViewControllerKey UITransitionContextToViewControllerKey
        UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController * toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        
        // [step.2] 获取当前的容器视图
        UIView * contentView = [transitionContext containerView];
        
        // 添加原本与目标的视图
        [contentView addSubview:fromVC.view];
        [contentView addSubview:toVC.view];
        
        // 设置目标视图大小
        toVC.view.frame = CGRectMake(0, fromVC.view.frame.size.height, fromVC.view.frame.size.width, 200);
        
        // [step.3] 动画的处理 <如果你是从视图上的某一个View扩展到一个新的视图, 这个时候需要给目标VC的View加上蒙版, 对蒙版进行动画操作。>
        
        // 下边的操作比较通用
        
        /*!
         * `[CABasicAnimation animationWithKeyPath:@"frame"];` keyPath 为支持Animation的属性名称
         * 详见这篇文章: `http://blog.csdn.net/majiakun1/article/details/46426727`
         */
        CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
        
        /*!
         * 动画的初始值, 这里的类型是id.
         */
    //    animation.fromValue = [NSValue valueWithCGRect:nil];
        
        /*!
         * 动画的结束值, 这里的类型是id. 当然还有byValue 动画的参考值,这里我们把y上移动200
         */
        animation.toValue = @(-200);
        /*!
         * 动画持续时间.
         */
        animation.duration = .2;
        /*!
         * 动画函数, 这里有4种效果。
         * - kCAMediaTimingFunctionLinear选项创建了一个线性的计时函数,同样也是CAAnimation的timingFunction属性为空时候的默认函数。线性步调对于那些立即加速并且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹),但是默认来说它看起来很奇怪,因为对大多数的动画来说确实很少用到。
         * - kCAMediaTimingFunctionEaseIn常量创建了一个慢慢加速然后突然停止的方法。对于之前提到的自由落体的例子来说很适合,或者比如对准一个目标的导弹的发射。
         * - kCAMediaTimingFunctionEaseOut则恰恰相反,它以一个全速开始,然后慢慢减速停止。它有一个削弱的效果,应用的场景比如一扇门慢慢地关上,而不是砰地一声。
         * - kCAMediaTimingFunctionEaseInEaseOut创建了一个慢慢加速然后再慢慢减速的过程。这是现实世界大多数物体移动的方式,也是大多数动画来说最好的选择。如果只可以用一种缓冲函数的话,那就必须是它了。那么你会疑惑为什么这不是默认的选择,实际上当使用UIView的动画方法时,他的确是默认的,但当创建CAAnimation的时候,就需要手动设置它了。
         */
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
        /*
         * 下边2行代码, 是否在结束时移除动画效果, presentingLayer
         */
        animation.removedOnCompletion = false;
        animation.fillMode = kCAFillModeForwards;
        animation.delegate = self;
        
        [toVC.view.layer addAnimation:animation forKey:@"frameAnimation"];}
    
    // This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
    - (void)animationEnded:(BOOL) transitionCompleted {
        
        // 动画结束, 完成转场
        [self.transitionContext completeTransition:YES];
    }
    
    @end
    

    至此,一个简单的转场动画就OK了。 效果很简单,就是想ActionSheet一样从底部弹出来。

    当然,这里的动画随你搞,View随你改变。知道了原理,相必大家就会用了,直接去网上搞个pop或者canvas的动画库、或者自己去写动画,总之、一切随你所愿。当然模态的话,dismissedController就是你要给定的消失动画需要覆写的方法。

    UIPercentDrivenInteractiveTransition
    • 有手势交互的动画,比如说,拖动控制这个vc一点点变化。
      这里我留下伏笔 UIPercentDrivenInteractiveTransition
    CAAnimation

    其实看到最后, 今天主要是想聊聊动画,平时不管是开发一些框架性的东西还是开发一些业务相关的东西,很少解除到动画。

    一般的来说:

    一个复杂的动画 最终都将变成 几个简单的动画。

    一个复杂的动画效果 最终都将变成简单的数学计算。

    比如说我们模拟一个弹簧的效果

    @interface ViewController ()
    @property (nonatomic, weak) IBOutlet UIView *containerView;
    @property (nonatomic, strong) UIImageView *ballView;
    @end
    @implementation ViewController
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        //add ball image view
        UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];
        self.ballView = [[UIImageView alloc] initWithImage:ballImage];
        [self.containerView addSubview:self.ballView];
        //animate
        [self animate];
    }
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        //replay animation on tap
        [self animate];
    }
    - (void)animate
    {
        //reset ball to top of screen
        self.ballView.center = CGPointMake(150, 32);
        //create keyframe animation
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
        animation.keyPath = @"position";
        animation.duration = 1.0;
        animation.delegate = self;
        animation.values = @[
                             [NSValue valueWithCGPoint:CGPointMake(150, 32)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 140)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 220)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 250)],
                             [NSValue valueWithCGPoint:CGPointMake(150, 268)]
                             ];
        animation.timingFunctions = @[
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                      [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]
                                      ];
        animation.keyTimes = @[@0.0, @0.3, @0.5, @0.7, @0.8, @0.9, @0.95, @1.0];
        //apply animation
        self.ballView.layer.position = CGPointMake(150, 268);
        [self.ballView.layer addAnimation:animation forKey:nil];
    }
    @end
    

    其实一个效果被拆分成了若干个细分的动作。

    再比如说 我们看到手机QQ的消息数量气泡可以拖动,其实这个动画可以慢慢的简化为几个小的组合动画,然后通过取到关键的点与控制点来控制小球的动画。

    Simulator Screen Shot 2016年3月30日 下午1.51.49.png

    就像我们模拟一个小球的变换一样。 实际上,这个小球由4段弧线组成。

    
        UIBezierPath * rectPath = [UIBezierPath bezierPath];
        [rectPath moveToPoint:A];
        [rectPath addCurveToPoint:B controlPoint1:A_1 controlPoint2:A_2];
        [rectPath addCurveToPoint:C controlPoint1:B_1 controlPoint2:B_2];
        [rectPath addCurveToPoint:D controlPoint1:C_1 controlPoint2:C_2];
        [rectPath addCurveToPoint:A controlPoint1:D_1 controlPoint2:D_2];
        
        [[UIColor redColor] setStroke];
        [[UIColor redColor] setFill];
        
        [rectPath fill];
        
        [rectPath stroke];
    

    每一段弧线之间又有2个控制点,实际上我们就是控制这些点的动作就可以简单的恩完成这个动画。

    下边附上代码,我就懒得写了(这些东西也都是慢慢的学习一些碎片化的知识。但是需要整理。我也在之前的博客中发出了原pdf,大家可以下载学习,感谢yang)。顺便附上一个非常好的效果 Loading

    以下是变化小球的代码, 很简单一看就懂。

    .h

    
    #import <UIKit/UIKit.h>
    
    typedef NS_OPTIONS(NSInteger, BZDirection) {
    
        BZDirection_D = 0,
        BZDirection_B = 1,
    };
    
    @interface BZView : UIView
    
    @property ( nonatomic, assign ) CGFloat progress;
    
    @property ( nonatomic, assign ) BZDirection direction;
    
    @property ( nonatomic, assign ) CGRect boxFrame;
    
    @end
    

    .m

    //
    //  BZView.m
    //  AnimationDemo
    //
    //  Created by Dylan on 16/3/28.
    //  Copyright © 2016年 Dylan. All rights reserved.
    //
    
    #import "BZView.h"
    
    #define BoxSize 90 /* 外框大小 */
    #define AnimatedWidth [UIScreen mainScreen].bounds.size.width - BoxSize /* 限制活动范围 */
    
    @implementation BZView
    
    - (instancetype) initWithFrame: (CGRect) frame {
        
        self = [super initWithFrame:frame];
        if ( self ) {
            
            self.backgroundColor = [UIColor whiteColor];
            
            self.progress = 0.5;
        }
        
        return self;
    }
    
    - (void) drawRect: (CGRect) rect {
        
        // Draw
        
        // Dash
        CGFloat dashArray[3];
        dashArray[0] = 3;
        dashArray[1] = 3;
        dashArray[2] = 3;
        
        // 外边辅助边框
        UIBezierPath * boxPath = [UIBezierPath bezierPathWithRect:_boxFrame];
        [[UIColor greenColor] setStroke];
        boxPath.lineWidth = 1;
        [boxPath setLineDash:dashArray count:3 phase:1];
        [boxPath stroke];
        
        // 控制点的偏移量
        CGFloat offSet = BoxSize / 3.6;
        
        // 每次变化progress后, 偏移量的计算
        CGFloat moveOffSet = (BoxSize * .2) * fabs( self.progress - .5 ) * 2;
        
        // 外部矩形的X, Y
        CGFloat boxX = self.boxFrame.origin.x;
        CGFloat boxY = self.boxFrame.origin.y;
        
        // 外部矩形的中心
        CGPoint boxCenter = CGPointMake(boxX + BoxSize / 2., boxY + BoxSize / 2.);
        
        // 圆
        CGPoint A = CGPointMake(boxCenter.x, boxY + moveOffSet);
        CGPoint B = CGPointMake(self.direction == BZDirection_D ? boxX + BoxSize : boxX + BoxSize + moveOffSet * 2., boxCenter.y);
        CGPoint C = CGPointMake(boxCenter.x, boxY + BoxSize - moveOffSet);
        CGPoint D = CGPointMake(self.direction == BZDirection_D ? boxX - moveOffSet * 2 : boxX, boxCenter.y);
        
        // 控制点
        CGPoint A_1 = CGPointMake(A.x + offSet, A.y);
        CGPoint A_2 = CGPointMake(B.x, self.direction == BZDirection_D ? B.y - offSet : B.y - offSet + moveOffSet);
        
        CGPoint B_1 = CGPointMake(B.x, self.direction == BZDirection_D ? B.y + offSet : B.y + offSet - moveOffSet);
        CGPoint B_2 = CGPointMake(C.x + offSet, C.y);
        
        CGPoint C_1 = CGPointMake(C.x - offSet, C.y);
        CGPoint C_2 = CGPointMake(D.x, self.direction == BZDirection_D ? D.y + offSet - moveOffSet : D.y + offSet);
        
        CGPoint D_1 = CGPointMake(D.x, self.direction == BZDirection_D ? D.y - offSet + moveOffSet : D.y - offSet);
        CGPoint D_2 = CGPointMake(A.x - offSet, A.y);
        
        // 圆形
        UIBezierPath * rectPath = [UIBezierPath bezierPath];
        [rectPath moveToPoint:A];
        [rectPath addCurveToPoint:B controlPoint1:A_1 controlPoint2:A_2];
        [rectPath addCurveToPoint:C controlPoint1:B_1 controlPoint2:B_2];
        [rectPath addCurveToPoint:D controlPoint1:C_1 controlPoint2:C_2];
        [rectPath addCurveToPoint:A controlPoint1:D_1 controlPoint2:D_2];
        
        [[UIColor redColor] setStroke];
        [[UIColor redColor] setFill];
        
        [rectPath fill];
        
        [rectPath stroke];
        
        // 控制点 连接
        
        UIBezierPath * control = [UIBezierPath bezierPath];
        
        [control moveToPoint:A];
        [control addLineToPoint:A_1];
        [control addLineToPoint:A_2];
        
        [control addLineToPoint:B];
        [control addLineToPoint:B_1];
        [control addLineToPoint:B_2];
        
        [control addLineToPoint:C];
        [control addLineToPoint:C_1];
        [control addLineToPoint:C_2];
        
        [control addLineToPoint:D];
        [control addLineToPoint:D_1];
        [control addLineToPoint:D_2];
        
        [control addLineToPoint:A];
        
        [control setLineDash:dashArray count:3 phase:1];
        
        [[UIColor blueColor] setStroke];
        
        [control stroke];
        
        [control appendPath:({
            UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:A radius:3 startAngle:0 endAngle:M_PI * 4 clockwise:YES];
            [[UIColor purpleColor] setFill];
            [path fill];
            path;
        })];
    }
    
    - (void) setProgress: (CGFloat) progress {
        
        // 设置方向
        if ( progress <= 0.5 ) {
            
            _direction = BZDirection_B;
            
            NSLog(@"B 点 移动");
        } else {
            
            _direction = BZDirection_D;
            NSLog(@"D 点 移动");
        }
        
        _progress = progress;
        
        // 重置外部边框大小
        
        CGFloat x = self.center.x - BoxSize / 2. + ( progress - .5 ) * ( AnimatedWidth - BoxSize );
        CGFloat y = self.center.y - BoxSize / 2.;
        
        _boxFrame = CGRectMake(x, y, BoxSize, BoxSize);
        
        [self setNeedsDisplay];
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:OC-VC转场动画教学、动画知识浅谈

        本文链接:https://www.haomeiwen.com/subject/vkkslttx.html