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

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

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

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

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

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

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

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

UIViewControllerAnimatedTransitioning

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

@protocol UIViewControllerAnimatedTransitioning 

// 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 )transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id )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 
#import 

@interface AnimatedVC : NSObject

@end
  • 在.M中实现方法(下边的说明我在代码注释中写明)
//
//  AnimatedVC.m
//  Created by Dylan on 16/3/29.
//

#import "AnimatedVC.h"

@interface AnimatedVC ()

@property ( nonatomic, strong ) id  transitionContext;

@end

@implementation AnimatedVC

- (NSTimeInterval)transitionDuration:(nullable id )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 )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的消息数量气泡可以拖动,其实这个动画可以慢慢的简化为几个小的组合动画,然后通过取到关键的点与控制点来控制小球的动画。

OC-VC转场动画教学、动画知识浅谈_第1张图片
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 

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转场动画教学、动画知识浅谈)