iOS CAAnimation动画初探

先看看CAAnimation动画的继承结构

CAAnimation{
    CAPropertyAnimation {
      CABasicAnimation {
        CASpringAnimation
        }
      CAKeyframeAnimation
      }
      CATransition
      CAAnimationGroup
 }

CAAnimation基本属性详解

//动画的动作规则,包含以下值
//kCAMediaTimingFunctionLinear 匀速
//kCAMediaTimingFunctionEaseIn 慢进快出
//kCAMediaTimingFunctionEaseOut 快进慢出
//kCAMediaTimingFunctionEaseInEaseOut 慢进慢出 中间加速
//kCAMediaTimingFunctionDefault 默认
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
//动画的代理回调
@property(nullable, strong) id <CAAnimationDelegate> delegate;
//动画执行完以后是否移除动画,默认YES
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

以上属性详解:

  • delegate:动画执行的代理,在动画开始前设定,不用显式的写在代码里,它包含两个方法:

动画开始回调
- (void)animationDidStart:(CAAnimation *)aim;
动画结束回调
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

  • removedOnCompletion:动画完成后是否移除动画.默认为YES.此属性为YES时, fillMode不可用,具体为什么不可用,可以自己结合两个属性分析一下,这里不再赘述.
  • timingFunction 设置动画速度曲线,默认值上面已经给出.下面说它的几个方法:

这两个方法是一样的.如果我们对系统自带的速度函数不满意,可以通过这两个函数创建一个自己喜欢的速度曲线函数,具体用法可以参考这篇文章CAMediaTimingFunction的使用

+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

获取曲线函数的缓冲点,具体用法可以参考这篇文章:iOS-核心动画高级编程/10-缓冲
- (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr;

CAAnimation协议的属性

//开始时间.这个属性比较复杂,傻瓜用法为:CACurrentMediaTime() + x,
//其中x为延迟时间.如果设置 beginTime = CACurrentMediaTime() + 1.0,产生的效果为延迟一秒执行动画,下面详解原理
@property CFTimeInterval beginTime;
//动画执行时间,此属性和speed有关系speed默认为1.0,如果speed设置为2.0,那么动画执行时间则为duration*(1.0/2.0).
@property CFTimeInterval duration;
//动画执行速度,它duration的关系参考上面解释
@property float speed;
//动画的时间延迟,这个属性比较复杂,下面详解
@property CFTimeInterval timeOffset;
//重复执行次数
@property float repeatCount;
//重复执行时间,此属性优先级大于repeatCount.也就是说如果repeatDuration设置为1秒重复10次,那么它会在1秒内执行完动画.
@property CFTimeInterval repeatDuration;
//是否自动翻转动画,默认NO.如果设置YES,那么整个动画的执行效果为A->B->A.
@property BOOL autoreverses;
//动画的填充方式,默认为: kCAFillModeRemoved,包含以下值
//kCAFillModeForwards//动画结束后回到准备状态
//kCAFillModeBackwards//动画结束后保持最后状态
//kCAFillModeBoth//动画结束后回到准备状态,并保持最后状态
//kCAFillModeRemoved//执行完成移除动画
@property(copy) NSString *fillMode;

以上属性的详解:

  • beginTime:刚才上面简单解释了下这个属性的用法:CACurrentMediaTime()+ x 会使动画延迟执行x秒.不知道到这里有没有人想过如果-x会出现怎么样效果?假设我们有执行一个3秒的动画,然后设置beginTime = CACurrentMediaTime()- 1.5那么执行动画你会发现动画只会执行后半段,也就是只执行后面的3-1.5s的动画.为什么会这样?其实动画都有一个timeline(时间线)的概念.动画开始执行都是基于这个时间线的绝对时间,这个时间和它的父类有关(系统的属性注释可以看到).默认的CALayer的beginTime为零,如果这个值为零的话,系统会把它设置为CACurrentMediaTime(),那么这个时间就是正常执行动画的时间:立即执行.所以如果你设置beginTime=CACurrentMediaTime()+x;它会把它的执行时间线推迟x秒,也就是晚执行x秒,如果你beginTime=CACurrentMediaTime()-x;那它开始的时候会从你动画对应的绝对时间开始执行.
  • timeOffset:时间偏移量,默认为0;既然它是时间偏移量,那么它即和动画时间相关.这么解释:假设我们设置一个动画时间为5s,动画执行的过程为1->2->3->4->5,这时候如果你设置timeOffset = 2s那么它的执行过程就会变成3->4->5->1->2如果你设置timeOffset = 4s那么它的执行过程就会变成5->1->2->3->4,这么说应该很明白了吧?

CAPropertyAnimation的基本属性

//需要动画的属性值
@property(nullable, copy) NSString *keyPath;
//属性动画是否以当前动画效果为基础,默认为NO
@property(getter=isAdditive) BOOL additive;
//指定动画是否为累加效果,默认为NO
@property(getter=isCumulative) BOOL cumulative;
//此属性相当于CALayer中的transform属性,下面会详解
@property(nullable, strong) CAValueFunction *valueFunction;

以上属性的详解:

CAPropertyAnimation是属性动画.顾名思义也就是针对属性才可以做的动画.那它可以对谁的属性可以做动画?是CALayer的属性,比如:bounds,position等.那么问题来了,我们改变CALayer的position可以直接设置[CAPropertyAnimation animationWithKeyPath:@"position"]如果我们设置它的transform(CATransform3D)呢?CATransform3D是一个矩阵,如果我们想为它做动画怎么办?下面这个属性就是用来解决这个问题的.

  • valueFunction:我们来看它可以设置的值:
kCAValueFunctionRotateX
kCAValueFunctionRotateY
kCAValueFunctionRotateZ
kCAValueFunctionScale
kCAValueFunctionScaleX
kCAValueFunctionScaleY
kCAValueFunctionScaleZ
kCAValueFunctionTranslate
kCAValueFunctionTranslateX
kCAValueFunctionTranslateY
kCAValueFunctionTranslateZ```

CAPropertyAnimation的方法

//通过key创建一个CAPropertyAnimation对象

  • (instancetype)animationWithKeyPath:(nullable NSString *)path;
下面我们来看一下可以设置属性动画的属性归总:

CATransform3D{
rotation旋转
transform.rotation.x
transform.rotation.y
transform.rotation.z

scale缩放
transform.scale.x
transform.scale.y
transform.scale.z

translation平移
transform.translation.x
transform.translation.y
transform.translation.z

}

CGPoint{
position
position.x
position.y
}

CGRect{
bounds
bounds.size
bounds.size.width
bounds.size.height

bounds.origin
bounds.origin.x
bounds.origin.y

}

property{
opacity
backgroundColor
cornerRadius
borderWidth
contents

Shadow{
    shadowColor
    shadowOffset
    shadowOpacity
    shadowRadius
}

}

总结: **CAAnimation**是基类,* *CAPropertyAnimation**是抽象类,两者都不可以直接使用, 那我们只有使用它的子类了.

## CABasicAnimation基本动画

### CABasicAnimation的属性

//开始值
@property(nullable, strong) id fromValue;
//结束值
@property(nullable, strong) id toValue;
//结束值
@property(nullable, strong) id byValue;


这三个属性之间的规则

* fromValue和toValue不为空,动画的效果会从fromValue的值变化到toValue.
* fromValue和byValue都不为空,动画的效果将会从fromValue变化到fromValue+byValue
* toValue和byValue都不为空,动画的效果将会从toValue-byValue变化到toValue
* 只有fromValue的值不为空,动画的效果将会从fromValue的值变化到当前的状态.
* 只有toValue的值不为空,动画的效果将会从当前状态的值变化到toValue的值.
* 只有byValue的值不为空,动画的效果将会从当前的值变化到(当前状态的值+byValue)的值.

CABasicAnimation看起来不太复杂,但实际只用这个就足以可以做很多种动画了,下面简单用一下,先看效果:
        
![CABasicAnimation.gif](http://upload-images.jianshu.io/upload_images/2455429-518087d3167a598f.gif?imageMogr2/auto-orient/strip)

实现代码:
  • (IBAction)animation:(id)sender
    {
    UIButton *btn = (UIButton *)sender;
    CABasicAnimation *animation = nil;

    switch (btn.tag)
    {
    //淡如淡出
    case 0:
    animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    [animation setFromValue:@1];
    [animation setToValue:@0.1];
    break;
    //缩放
    case 1:
    animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    [animation setFromValue:@1];
    [animation setToValue:@0.1];
    break;
    //旋转
    case 2:
    animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    [animation setToValue:@(M_PI)];
    break;
    //平移
    case 3:
    animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setToValue:[NSValue valueWithCGPoint:CGPointMake(self.opView.center.x, self.opView.center.y+200)]];
    break;
    default:
    break;
    }

    animation.delegate = self;
    animation.duration = 0.3;//设置动画时间,单次动画时间
    animation.removedOnCompletion = NO;//默认为YES,设置为NO时setFillMode有效

    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

    //设置自动翻转
    //设置自动翻转以后单次动画时间不变,总动画时间增加一倍,它会让你前半部分的动画以相反的方式动画过来
    //比如说你设置执行一次动画,从a到b时间为1秒,设置自动翻转以后动画的执行方式为,先从a到b执行一秒,然后从b到a再执行一下动画结束
    [animation setAutoreverses:YES];

    //kCAFillModeForwards//动画结束后回到准备状态
    //kCAFillModeBackwards//动画结束后保持最后状态
    //kCAFillModeBoth//动画结束后回到准备状态,并保持最后状态
    //kCAFillModeRemoved//执行完成移除动画
    [animation setFillMode:kCAFillModeBoth];

    //将动画添加到layer,添加到图层开始执行动画,
    //注意:key值的设置与否会影响动画的效果
    //如果不设置key值每次执行都会创建一个动画,然后创建的动画会叠加在图层上
    //如果设置key值,系统执行这个动画时会先检查这个动画有没有被创建,如果没有的话就创建一个,如果有的话就重新从头开始执行这个动画
    //你可以通过key值获取或者删除一个动画:
    [self.opView.layer addAnimation:animation forKey:@"baseanimation"];
    }

###CASpringAnimation弹性动画
**CASpringAnimation的**属性(iOS9新加)

//理解下面的属性的时候可以结合现实物理现象,比如把它想象成一个弹簧上挂着一个金属小球
//质量,振幅和质量成反比
@property CGFloat mass;
//刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快
@property CGFloat stiffness;
//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快,可以认为它是阻力系数
@property CGFloat damping;
//初始速率,动画视图的初始速度大小速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反.
@property CGFloat initialVelocity;
//结算时间,只读.返回弹簧动画到停止时的估算时间,根据当前的动画参数估算通常弹簧动画的时间使用结算时间比较准确
@property(readonly) CFTimeInterval settlingDuration;


下面我们写一个demo看看效果:


![](//upload-images.jianshu.io/upload_images/2455429-953b571f7664e583.gif)

实现代码:
  • (IBAction)animationBegin:(id)sender
    {
    UIButton *btn = (UIButton *)sender;

    CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position.y"];
    springAnimation.mass = 1;
    springAnimation.stiffness = 100;
    springAnimation.damping = 1;
    springAnimation.initialVelocity = 0;
    springAnimation.duration = springAnimation.settlingDuration;
    springAnimation.fromValue = @(self.opView.center.y);
    springAnimation.toValue = @(self.opView.center.y + (btn.selected?+150:-150));
    springAnimation.fillMode = kCAFillModeForwards;
    [self.opView.layer addAnimation:springAnimation forKey:nil];

    btn.selected = !btn.selected;
    }


### CAKeyframeAnimation关键帧动画
**CAKeyframeAnimation**的属性

//关键帧值数组,一组变化值
@property(nullable, copy) NSArray *values;
//关键帧帧路径,优先级比values大
@property(nullable) CGPathRef path;
//每一帧对应的时间,时间可以控制速度.它和每一个帧相对应,取值为0.0-1.0,不设则每一帧时间相等.
@property(nullable, copy) NSArray *keyTimes;
//每一帧对应的时间曲线函数,也就是每一帧的运动节奏
@property(nullable, copy) NSArray *timingFunctions;
//动画的计算模式,默认值: kCAAnimationLinear.有以下几个值:
//kCAAnimationLinear//关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
//kCAAnimationDiscrete//离散的,也就是没有补间动画
//kCAAnimationPaced//平均,keyTimes跟timeFunctions失效
//kCAAnimationCubic对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整自定义,keyTimes跟timeFunctions失效
//kCAAnimationCubicPaced在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,,keyTimes跟timeFunctions失效
@property(copy) NSString *calculationMode;
//动画的张力,当动画为立方计算模式的时候此属性提供了控制插值,因为每个关键帧都可能有张力所以连续性会有所偏差它的范围为[-1,1].同样是此作用
@property(nullable, copy) NSArray *tensionValues;
//动画的连续性值
@property(nullable, copy) NSArray *continuityValues;
//动画的偏斜率
@property(nullable, copy) NSArray *biasValues;
//动画沿路径旋转方式,默认为nil.它有两个值:
//kCAAnimationRotateAuto//自动旋转,
//kCAAnimationRotateAutoReverse//自动翻转
@property(nullable, copy) NSString *rotationMode;


**CAKeyframeAnimation**可以做很丰富的效果,下面展示了几种纯**CAKeyframeAnimation**做的效果:


![](//upload-images.jianshu.io/upload_images/2455429-4d17bc8a5d9b2bb1.gif)

实现代码:
  • (IBAction)animationBegin:(id)sender {
    UIButton *btn = (UIButton *)sender;
    switch (btn.tag) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    [self path:btn.tag];break;
    case 6:
    case 7:
    [self values:btn.tag];break;

      default:
          break;
    

    }
    }

-(void)path:(NSInteger)tag{

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

switch (tag) {
    case 0:{
        //椭圆
        CGMutablePathRef path = CGPathCreateMutable();//创建可变路径
        CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 320, 500));
        [animation setPath:path];
        CGPathRelease(path);
        animation.rotationMode = kCAAnimationRotateAuto;
        
    }break;
    case 1:{
        //贝塞尔,矩形
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 320, 320)];
        //animation需要的类型是CGPathRef,UIBezierPath是ui的,需要转化成CGPathRef
        [animation setPath:path.CGPath];
        
        
    }break;
    case 2:{
        //贝塞尔,抛物线
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:self.opView.center];
        [path addQuadCurveToPoint:CGPointMake(0, 568)
                     controlPoint:CGPointMake(400, 100)];
        [animation setPath:path.CGPath];
        
    }break;
    case 3:{
        //贝塞尔,s形曲线
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointZero];
        [path addCurveToPoint:self.opView.center
                controlPoint1:CGPointMake(320, 100)
                controlPoint2:CGPointMake(  0, 400)];
        ;
        [animation setPath:path.CGPath];
        
        
    }break;
    case 4:{
        //贝塞尔,圆形
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center
                                                            radius:150
                                                        startAngle:- M_PI * 0.5
                                                          endAngle:M_PI * 2
                                                         clockwise:YES];
        [animation setPath:path.CGPath];
        
        
    }break;
    case 5:{
        CGPoint point = CGPointMake(self.view.center.x, 400);
        CGFloat xlength = point.x - self.opView.center.x;
        CGFloat ylength = point.y - self.opView.center.y;
        
        CGMutablePathRef path = CGPathCreateMutable();
        //移动到目标点
        CGPathMoveToPoint(path, NULL, self.opView.center.x, self.opView.center.y);
        //将目标点的坐标添加到路径中
        CGPathAddLineToPoint(path, NULL, point.x, point.y);
        //设置弹力因子,
        CGFloat offsetDivider = 5.0f;
        BOOL stopBounciong = NO;
        while (stopBounciong == NO) {
            CGPathAddLineToPoint(path, NULL, point.x + xlength / offsetDivider, point.y + ylength / offsetDivider);
            CGPathAddLineToPoint(path, NULL, point.x, point.y);
            offsetDivider += 6.0;
            //当视图的当前位置距离目标点足够小我们就退出循环
            if ((ABS(xlength / offsetDivider) < 10.0f) && (ABS(ylength / offsetDivider) < 10.0f)) {
                break;
            }
        }
        [animation setPath:path];
        
    }break;
    default:break;
}

[animation setDuration:0.5];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeBoth];
[self.opView.layer addAnimation:animation forKey:nil];

}

-(void)values:(NSInteger)tag{

CAKeyframeAnimation *animation = nil;
switch (tag) {
    case 6:{
        animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
        
        CGFloat angle = M_PI_4 * 0.5;
        NSArray *values = @[@(angle),@(-angle),@(angle)];
        [animation setValues:values];
        [animation setRepeatCount:3];
        [animation setDuration:0.5];
    }break;
    case 7:{
        
        
        animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        NSValue *p1 = [NSValue valueWithCGPoint:self.opView.center];
        NSValue *p2 = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x + 100, 200)];
        NSValue *p3 = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x, 300)];
        //设置关键帧的值
        [animation setValues:@[p1,p2,p3]];
        [animation setDuration:0.5];
    }break;
    default:break;
}

UIGraphicsBeginImageContext(self.view.frame.size);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeBoth];
[self.opView.layer addAnimation:animation forKey:nil];

}

### CAAnimationGroup动画组
**CAAnimationGroup**的属性

//只有一个属性,数组中接受CAAnimation元素
@property(nullable, copy) NSArray *animations;

可以看到CAAnimationGroup只有一个属性一个CAAnimation数组.而且它继承于CAAnimation,它具有CAAnimation的特性,所以它的用法和CAAnimation是一样的,不同的是他可以包含n个动画,也就是说他可以接受很多个CAAnimation并且可以让它们一起开始,这就造成了动画效果的叠加,效果就是n个动画同时进行.

来看一个简单的效果:


![](//upload-images.jianshu.io/upload_images/2455429-5cce294ff5a60e70.gif)

实现代码:
  • (IBAction)animationBegin:(id)sender
    {
    CAAnimationGroup *group = [CAAnimationGroup animation];

    /**

    • 移动动画
      /
      CAKeyframeAnimation position = [self moveAnimation];
      /
    • 摇晃动画
      /
      CAKeyframeAnimation shake = [self shakeAnimation];
      /
    • 透明度动画
      /
      CABasicAnimation alpha = [self alphaAnimation];
      /
    • 设置动画组的时间,这个时间表示动画组的总时间,它的子动画的时间和这个时间没有关系
      */
      [group setDuration:3.0];
      [group setAnimations:@[position,shake,alpha]];
      [self.opView.layer addAnimation:group forKey:nil];
      }

pragma mark -- CAKeyframeAnimation - 路径平移动画

-(CAKeyframeAnimation )moveAnimation
{
CAKeyframeAnimation animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
/

* 设置路径,按圆运动
*/
CGMutablePathRef path = CGPathCreateMutable();//CG是C语言的框架,需要直接写语法
CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 320, 320));
[animation setPath:path];//把路径给动画
CGPathRelease(path);//释放路径

/**
 *  设置动画时间,这是动画总时间,不是每一帧的时间
 */
[animation setDuration:3];
/**
 *setRemovedOnCompletion 设置动画完成后是否将图层移除掉,默认是移除
 *setFillMode 当前设置的是向前填充,意味着动画完成后填充效果为最新的效果,此属性有效的前提是 setRemovedOnCompletion=NO
 *注意:
 *1.动画只是改变人的视觉,它并不会改变视图的初始位置等信息,也就是说无论动画怎么东,都不会改变view的原始大小,只是看起来像是大小改变了而已
 *2.因为没有改变视图的根本大小,所以视图所接收事件的位置还是原来的大小,可以不是显示的大小
 */
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeForwards];
return animation;

}

pragma mark -- CAKeyframeAnimation - 摇晃动画

-(CAKeyframeAnimation )shakeAnimation
{
CAKeyframeAnimation animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
/

* 设置路径,贝塞尔路径
/
CGFloat angle = M_PI_4 * 0.1;
NSArray values = @[@(angle),@(-angle),@(angle)];
[animation setValues:values];
[animation setRepeatCount:10];
/

* 设置动画时间,这是动画总时间,不是每一帧的时间
*/
[animation setDuration:0.25];
return animation;
}

pragma mark -- CABasicAnimation - 淡如淡出动画

-(CABasicAnimation )alphaAnimation
{
CABasicAnimation animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setDuration:1.0];
/

* 设置重复次数
/
[animation setRepeatCount:3];
/
*
* 设置自动翻转
* 设置自动翻转以后单次动画时间不变,总动画时间延迟一倍,它会让你前半部分的动画以相反的方式动画过来
* 比如说你设置执行一次动画,从a到b时间为1秒,设置自动翻转以后动画的执行方式为,先从a到b执行一秒,然后从b到a再执行一下动画结束
/
[animation setAutoreverses:YES];
/
*
* 设置起始值
/
[animation setFromValue:@1.0];
/
*
* 设置目标值
/
[animation setToValue:@0.1];
/
*
* 将动画添加到layer 添加到图层开始执行动画,
* 注意:key值的设置与否会影响动画的效果
* 如果不设置key值每次执行都会创建一个动画,然后创建的动画会叠加在图层上
* 如果设置key值,系统执行这个动画时会先检查这个动画有没有被创建,如果没有的话就创建一个,如果有的话就重新从头开始执行这个动画
* 你可以通过key值获取或者删除一个动画
*/
return animation;
}


### CATransition转场动画
**CATransition**属性

//转场类型,字符串类型参数.系统提供了四中动画形式:
//kCATransitionFade//逐渐消失
//kCATransitionMoveIn//移进来
//kCATransitionPush//推进来
//kCATransitionReveal//揭开
//另外,除了系统给的这几种动画效果,我们还可以使用系统私有的动画效果:
//@"cube",//立方体翻转效果
//@"oglFlip",//翻转效果
//@"suckEffect",//收缩效果,动画方向不可控
//@"rippleEffect",//水滴波纹效果,动画方向不可控
//@"pageCurl",//向上翻页效果
//@"pageUnCurl",//向下翻页效果
//@"cameralIrisHollowOpen",//摄像头打开效果,动画方向不可控
//@"cameraIrisHollowClose",//摄像头关闭效果,动画方向不可控
@property(copy) NSString *type;
//转场方向,系统一共提供四个方向:
//kCATransitionFromRight//从右开始
//kCATransitionFromLeft//从左开始
//kCATransitionFromTop//从上开始
//kCATransitionFromBottom//从下开始
@property(nullable, copy) NSString *subtype;
//开始进度,默认0.0.如果设置0.3,那么动画将从动画的0.3的部分开始
@property float startProgress;
//结束进度,默认1.0.如果设置0.6,那么动画将从动画的0.6部分以后就会结束
@property float endProgress;
//开始进度
@property(nullable, strong) id filter;

**CATransition**也是继承**CAAnimation**,系统默认提供了12种动画样式,加上4个动画方向,除了方向不可控的四种效果外,大概一共提供了36种动画.
另外系统还给UIView添加了很多分类方法可以快速完成一些简单的动画,如下:

**UIView(UIViewAnimation)**

@interface UIView(UIViewAnimation)

  • (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context; // additional context info passed to will start/did stop selectors. begin/commit can be nested
    //提交动画
  • (void)commitAnimations;
    //设置代理
  • (void)setAnimationDelegate:(nullable id)delegate; //设置动画开始方法
  • (void)setAnimationWillStartSelector:(nullable SEL)selector;
    //设置动画结束方法
  • (void)setAnimationDidStopSelector:(nullable SEL)selector;
    //设置动画时间:default = 0.2
  • (void)setAnimationDuration:(NSTimeInterval)duration;
    //设置动画延迟开始时间:default = 0.0
  • (void)setAnimationDelay:(NSTimeInterval)delay;
    //设置动画延迟开始日期:default = now ([NSDate date])
  • (void)setAnimationStartDate:(NSDate *)startDate;
    //设置动画运动曲线:default =UIViewAnimationCurveEaseInOut
    //UIViewAnimationCurveEaseInOut,//慢进慢出
    //UIViewAnimationCurveEaseIn, //慢进快出
    //UIViewAnimationCurveEaseOut,//快进慢出
    //UIViewAnimationCurveLinear//匀速
  • (void)setAnimationCurve:(UIViewAnimationCurve)curve;
    //设置重复次数: default = 0.0. May be fractional
  • (void)setAnimationRepeatCount:(float)repeatCount;
    //设置是否翻转动画: default = NO. used if repeat
  • (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;
    //设置动画是否从当前状态开始:default = NO
  • (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;
    //设置动画类型
  • (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
    //设置动画是否有效
  • (void)setAnimationsEnabled:(BOOL)enabled;
    //
  • (BOOL)areAnimationsEnabled;
    //
  • (void)performWithoutAnimation:(void (^)(void))actionsWithoutAnimation
    //
  • (NSTimeInterval)inheritedAnimationDuration
    @end

**UIView(UIViewAnimationWithBlocks)**

@interface UIView(UIViewAnimationWithBlocks)
//以下方法都大同小异,就不一一做注释了

  • (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
  • (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
  • (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations;
  • (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
  • (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
  • (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion;
  • (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

@end


**UIView (UIViewKeyframeAnimations)**
  • (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
  • (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations
以上方法比较多,找值得说的简单说一下吧:

//单视图转场动画

  • (void)transitionWithView:(UIView *)view
    duration:(NSTimeInterval)duration
    options:(UIViewAnimationOptions)options
    animations:(void (^ __nullable)(void))animations
    completion:(void (^ __nullable)(BOOL finished))completion
    //双视图转场动画
  • (void)transitionFromView:(UIView *)fromView
    toView:(UIView *)toView
    duration:(NSTimeInterval)duration
    options:(UIViewAnimationOptions)options
    completion:(void (^ __nullable)(BOOL finished))completion
这两个都是转场动画,不同的是第一个是单视图转场,第二个是双视图转场.不过需要注意的是:单视图转场动画只能用作属性动画做不到的转场效果,比如属性动画不能给UIImageview的image赋值操作做动画效果等.
我们可以看到以上两个方法中都有一个共同的参数:

**UIViewAnimationOptions**

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
UIViewAnimationOptionLayoutSubviews = 1 << 0,
UIViewAnimationOptionAllowUserInteraction = 1 << 1, // turn on user interaction while animating
UIViewAnimationOptionBeginFromCurrentState = 1 << 2, // start all views from current value, not initial value
UIViewAnimationOptionRepeat = 1 << 3, // repeat animation indefinitely
UIViewAnimationOptionAutoreverse = 1 << 4, // if repeat, run animation back and forth
UIViewAnimationOptionOverrideInheritedDuration = 1 << 5, // ignore nested duration
UIViewAnimationOptionOverrideInheritedCurve = 1 << 6, // ignore nested curve
UIViewAnimationOptionAllowAnimatedContent = 1 << 7, // animate contents (applies to transitions only)
UIViewAnimationOptionShowHideTransitionViews = 1 << 8, // flip to/from hidden state instead of adding/removing
UIViewAnimationOptionOverrideInheritedOptions = 1 << 9, // do not inherit any options or animation type

UIViewAnimationOptionCurveEaseInOut            = 0 << 16, // default
UIViewAnimationOptionCurveEaseIn               = 1 << 16,
UIViewAnimationOptionCurveEaseOut              = 2 << 16,
UIViewAnimationOptionCurveLinear               = 3 << 16,

UIViewAnimationOptionTransitionNone            = 0 << 20, // default
UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20,
UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20,
UIViewAnimationOptionTransitionCurlUp          = 3 << 20,
UIViewAnimationOptionTransitionCurlDown        = 4 << 20,
UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20,
UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20,
UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,

} NS_ENUM_AVAILABLE_IOS(4_0);

可以看到系统给到的是一个位移枚举,这就意味着这个枚举可以多个值同时使用,但是怎么用呢?其实那些枚举值可以分为三个部分.
我们分别看一下每个枚举的意思:

**第一部分:动画效果**

UIViewAnimationOptionTransitionNone//没有效果
UIViewAnimationOptionTransitionFlipFromLeft//从左水平翻转
UIViewAnimationOptionTransitionFlipFromRight//从右水平翻转
UIViewAnimationOptionTransitionCurlUp//翻书上掀
UIViewAnimationOptionTransitionCurlDown//翻书下盖UIViewAnimationOptionTransitionCrossDissolve//融合
UIViewAnimationOptionTransitionFlipFromTop//从上垂直翻转
UIViewAnimationOptionTransitionFlipFromBottom//从下垂直翻转

**第二部分:动画运动曲线**

//开始慢,加速到中间,然后减慢到结束
UIViewAnimationOptionCurveEaseInOut
//开始慢,加速到结束
UIViewAnimationOptionCurveEaseIn
//开始快,减速到结束
UIViewAnimationOptionCurveEaseOut
//线性运动
UIViewAnimationOptionCurveLinear

**第三部分:其他**

//默认,跟父类作为一个整体
UIViewAnimationOptionLayoutSubviews
//设置了这个,主线程可以接收点击事件
UIViewAnimationOptionAllowUserInteraction
//从当前状态开始动画,父层动画运动期间,开始子层动画.
UIViewAnimationOptionBeginFromCurrentState
//重复执行动画,从开始到结束, 结束后直接跳到开始态

**UIViewAnimationOptionRepeat**

//反向执行动画,结束后会再从结束态->开始态
UIViewAnimationOptionAutoreverse
//忽略继承自父层持续时间,使用自己持续时间(如果存在)
UIViewAnimationOptionOverrideInheritedDuration
//忽略继承自父层的线性效果,使用自己的线性效果(如果存在)
UIViewAnimationOptionOverrideInheritedCurve
//允许同一个view的多个动画同时进行
UIViewAnimationOptionAllowAnimatedContent
//视图切换时直接隐藏旧视图、显示新视图,而不是将旧视图从父视图移除(仅仅适用于转场动画) UIViewAnimationOptionShowHideTransitionViews
//不继承父动画设置或动画类型.
UIViewAnimationOptionOverrideInheritedOptions

这下可以看到,这些枚举功能都不一样但是可以随意组合,但是组合的时候需要注意,同一类型的枚举不能一起使用比如UIViewAnimationOptionCurveEaseIn和UIViewAnimationOptionCurveEaseOut
然后我们看一下转场动画的一些效果:

**单视图转场**


![单视图转场.gif](http://upload-images.jianshu.io/upload_images/2455429-9b985706162c02b9.gif?imageMogr2/auto-orient/strip)

**双视图转场**


![双视图转场.gif](http://upload-images.jianshu.io/upload_images/2455429-64442b994e0d1b76.gif?imageMogr2/auto-orient/strip)

**CATransition转场**


![CATransition转场.gif](http://upload-images.jianshu.io/upload_images/2455429-ef67070f27e696f1.gif?imageMogr2/auto-orient/strip)

实现代码:
  • (IBAction)animationBegin:(id)sender
    {
    UIButton *btn = (UIButton *)sender;
    switch (btn.tag)
    {
    case 0:
    [self animationSingleView:YES];
    break;
    case 1:
    [self animationSingleView:NO];
    break;
    case 2:[self chang3];
    break;
    default:break;
    }
    }

/**

  • 转场动画在执行过程中不可以被停止,
  • 转场动画在执行过程中不可以用户交互
  • 转场动画在执行过程中不可以控制动画执行进度
    /
    /
    *
  • 基于UIView的单视图转场动画
    */
    static NSUInteger change1_0Index = 0;
    static NSUInteger change1_1Index = 0;
    static NSUInteger change1_2Index = 0;

-(void)animationSingleView:(BOOL)sigle
{
/**
* 第一部分
*/
NSArray *array0 = @[
@(UIViewAnimationOptionTransitionNone),
@(UIViewAnimationOptionTransitionFlipFromLeft),//从左水平翻转
@(UIViewAnimationOptionTransitionFlipFromRight),//从右水平翻转
@(UIViewAnimationOptionTransitionCurlUp),//翻书上掀
@(UIViewAnimationOptionTransitionCurlDown),//翻书下盖
@(UIViewAnimationOptionTransitionCrossDissolve),//融合
@(UIViewAnimationOptionTransitionFlipFromTop),//从上垂直翻转
@(UIViewAnimationOptionTransitionFlipFromBottom),//从下垂直翻转
];

/**
 *  第二部分
 */
NSArray *array1 = @[
                    @(UIViewAnimationOptionCurveEaseInOut),////开始慢,加速到中间,然后减慢到结束
                    @(UIViewAnimationOptionCurveEaseIn),//开始慢,加速到结束
                    @(UIViewAnimationOptionCurveEaseOut),//开始快,减速到结束
                    @(UIViewAnimationOptionCurveLinear),//线性运动
                    ];

/**
 *  第三部分
 */
NSArray *array2 = @[
                    @(UIViewAnimationOptionLayoutSubviews),//默认,跟父类作为一个整体
                    @(UIViewAnimationOptionAllowUserInteraction),//设置了这个,主线程可以接收点击事件
                    @(UIViewAnimationOptionBeginFromCurrentState),//从当前状态开始动画,父层动画运动期间,开始子层动画。
                    @(UIViewAnimationOptionRepeat),//重复执行动画,从开始到结束, 结束后直接跳到开始态
                    @(UIViewAnimationOptionAutoreverse),//反向执行动画,结束后会再从结束态->开始态
                    @(UIViewAnimationOptionOverrideInheritedDuration),//忽略继承自父层持续时间,使用自己持续时间(如果存在)
                    @(UIViewAnimationOptionOverrideInheritedCurve),//忽略继承自父层的线性效果,使用自己的线性效果(如果存在)
                    @(UIViewAnimationOptionAllowAnimatedContent),//允许同一个view的多个动画同时进行
                    @(UIViewAnimationOptionShowHideTransitionViews),//视图切换时直接隐藏旧视图、显示新视图,而不是将旧视图从父视图移除(仅仅适用于转场动画)
                    @(UIViewAnimationOptionOverrideInheritedOptions),//不继承父动画设置或动画类型。
                    ];

//    CASpringAnimation
//    CASpringAnimation

if (sigle)
{
    
    [UIView transitionWithView:self.opView1
                      duration:1
                       options:
     ((NSNumber *)array0[change1_0Index]).integerValue|
     ((NSNumber *)array1[change1_1Index]).integerValue|
     ((NSNumber *)array2[change1_2Index]).integerValue
                    animations:^{
                        /**
                         *  单视图的转场动画需要在动画块中设置视图转场前的内容和视图转场后的内容
                         */
                        if (self.opView1.tag == 0)
                        {
                            self.opView1.image = [UIImage imageNamed:@"IMG_1730"];
                            self.opView1.tag = 1;
                        }
                        else
                        {
                            self.opView1.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
                            self.opView1.tag = 0;
                        }
                    } completion:nil];
    NSLog(@"动画:%s:%@:%@:%@",__func__,@(change1_0Index),@(change1_1Index),@(change1_2Index));
}
else
{
    /**
     *  双视图的转场动画
     *  注意:双视图的转场动画实际上是操作视图移除和添加到父视图的一个过程,from视图必须要有父视图,to视图必须不能有父视图,否则会出问题
     *  比如动画不准等
     */
    
    UIImageView *fromView = nil;
    UIImageView *toView = nil;
    
    if (self.opView1.tag == 0)
    {
        fromView = self.opView1;
        toView = self.opView2;
        self.opView1.tag = 1;
    }
    else
    {
        fromView = self.opView2;
        toView = self.opView1;
        self.opView1.tag = 0;
    }
    
    [UIView transitionFromView:fromView
                        toView:toView duration:1.0
                       options:
     ((NSNumber *)array0[change1_0Index]).integerValue|
     ((NSNumber *)array1[change1_1Index]).integerValue|
     ((NSNumber *)array2[change1_2Index]).integerValue
                    completion:^(BOOL finished) {
                        [fromView removeFromSuperview];
                    }];
    [self.view addSubview:toView];
}
change1_0Index += 1;
if (change1_0Index > array0.count - 1)
{
    change1_0Index = 0;
    change1_1Index += 1;
}
if (change1_1Index > array1.count - 1)
{
    change1_1Index = 0;
    change1_2Index += 1;
}
if (change1_2Index > array2.count - 1)
{
    change1_2Index = 0;
    
    change1_0Index = 0;
    change1_2Index = 0;
}

}

/**

  • 基于CATransition的视图转场动画
    /
    static NSUInteger change3_0Index = 0;
    static NSUInteger change3_1Index = 0;
    -(void)chang3
    {
    /
    *
    *创建转场动画:注意:CATransaction和CATransition 不一样
    */
    CATransition *transition = [CATransition animation];

    transition.duration = 0.25;

    NSArray *type_array = @[
    //系统提供的动画
    kCATransitionFade,
    kCATransitionMoveIn,
    kCATransitionPush,
    kCATransitionReveal,

                        //以下是私有api,只能字符串访问
                        @"cube",//立方体翻转效果
                        @"oglFlip",//翻转效果
                        @"suckEffect",//收缩效果,动画方向不可控
                        @"rippleEffect",//水滴波纹效果,动画方向不可控
                        @"pageCurl",//向上翻页效果
                        @"pageUnCurl",//向下翻页效果
                        @"cameralIrisHollowOpen",//摄像头打开效果,动画方向不可控
                        @"cameraIrisHollowClose",//摄像头关闭效果,动画方向不可控
                        ];
    

    //转场类型
    transition.type = type_array[change3_0Index];

    NSArray *subtype_array = @[
    kCATransitionFromRight,
    kCATransitionFromLeft,
    kCATransitionFromTop,
    kCATransitionFromBottom
    ];
    //转场方向
    transition.subtype = subtype_array[change3_1Index];

    /**

    • 设置转场动画的开始和结束百分比
      */
      transition.startProgress = 0.0;
      transition.endProgress = 1.0;
if (self.opView1.tag == 0)
{
    self.opView1.tag = 1;
    
    
    self.opView1.image = [UIImage imageNamed:@"IMG_1730"];
    self.opView2.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
}
else
{
    self.opView1.tag = 0;
    
    self.opView1.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
    self.opView2.image = [UIImage imageNamed:@"IMG_1730"];
}
[self.opView1.layer addAnimation:transition forKey:nil];
[self.opView2.layer addAnimation:transition forKey:nil];

NSLog(@"动画:%s:%@:%@",__func__,@(change3_0Index),@(change3_1Index));

change3_1Index += 1;
if (change3_1Index > subtype_array.count - 1)
{
    change3_1Index = 0;
    change3_0Index += 1;
}
if (change3_0Index > type_array.count - 1)
{
    change3_0Index = 0;
}

}

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

推荐阅读更多精彩内容