iOS核心动画总结

基础概念

iOS图形架构

       核心动画是 iOS 和 MacOS 上的图形渲染和动画基础结构,用于为应用的视图和其他视觉元素设置动画。
       核心动画位于 APPKit 和 UIKit 之下,且集成于 Cocoa 和 Cocoa Touch 的视图工作流之中。
       核心动画自己也具有一些接口,可以扩展视图所展现的功能,并可以更好的控制应用的动画。下图是iOS图形处理架构图:

iOS核心动画总结_第1张图片
图形界面架构

UIView与CALayer的关系与区别

       UIView:用户交互、界面展示。真正绘制的界面的类是Layer。CALayer属于QuartzCore框架(跨平台的)。mac os:交互上面:APPKIT,通过鼠标键盘交互,NSView。相同点:同一种绘制方式(CALayer)。不同点:不同交互方式。UIView封装了layer
frame,background:layer对应的属性。

       总结:UIView负责处理用户交互,CALayer负责绘制内容,每个View都有一个Layer。我们访问和设置的这些负责显示的属性实际上就是访问和设置了Layer对应的属性,只不过UIView把它封装了起来。

iOS核心动画总结_第2张图片
UIView与CALayer关系图.png

iOS 为什么要基于 UIView 和 CAlayer 提供2个平行的层级关系?
1、 职责分离:UIView 处理 UI 交互,CALayer 处理内容绘制和动画;
2、 代码公用:在 iOS 和 macOS 2个平台上,事件和UI 交互有许多不同点,基于触控和鼠标键盘交互有本质的区别;故 针对不同的平台,UI 交互这些代码做不同的处理,而内容绘制和动画这些代码可以复用。

UIView和Layer图层属性概念

UIView/CALayer 3个重要的属性:

  • frame:相对于父级的外部坐标
  • bounds:内部坐标
  • center/position:相当于初始anchorPoint(锚点概念在下面)在父级Layer中的位置。anchorPoint改变,center/position不会跟着改变。

UIView & CALayer 的坐标系

iOS核心动画总结_第3张图片
正常的坐标系

frame是一个虚拟属性,根据bounds,position,transform计算得来, 其中任一值发生变化,frame都会改变。
对Layer进行变换操作时,比如缩放或旋转,frame 代表的是覆盖在 Layer 旋转之后的整个轴对齐的矩形区域,既 frame 和 bounds 的宽高不再一致。
iOS核心动画总结_第4张图片
旋转后的坐标系.png

锚点:anchorPoint

可以认为是用来移动 Layer 的把柄。
默认居于 Layer 的中心点,可改变。示例如下:


iOS核心动画总结_第5张图片
iOS和OS X坐标系

实际应用,通过改变锚点,使 Layer 围绕其的一个端点旋转,如指针钟表。

显式动画/隐式动画

隐式动画

当改变CALayer的可动画属性时,它会从原先的值平滑过渡到新的值。默认时长为0.25s。即不指定任何动画类型,在改变CALayer的属性时,Core Animation来决定如何并且何时去做动画。UIView的Layer不能做隐式动画。
Core Animation是如何判断动画类型和持续时间的呢?
实际上动画执行的时间取决于当前事务的设置,动画类型取决于图层行为。

事务

Core Animation用来包含一些列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立即发生变化。而是当事务提交的时候开始使用一个动画过渡到新值。

事务使用CATransaction来进行管理。此类无实例,使用+begin和+commit来入栈和出栈。当改变图层属性值时,任何可动画的图层属性都会被加入到栈顶的事务,可以通过+setAnimationDUration:和+ animationDuration来设置/获取值(默认0.25s)。

Core Animation 在每个Runloop周期中自动开始一次新的事务,即使不显式的使用[CATransaction begin]开始一次事务,任何在一次Runloop循环中属性的改变都会被集中起来,然后做一次0.25s的动画。

图层行为

改变属性时,CALayer自动应用的动画称作行为(action),当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。实际步骤如下:
1、图层首先检测它是否有委托,并且事发后实现CALayerDelegate协议指定的-actionForLayer: forKey方法。如果有,直接调用并返回结果。
2、如果没有委托,或委托未实现-actionForLayer: forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
3、如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
4、最后,如果style中也找不到对应的行为,那么图层将会直接调用定义了每个属性标准行为的-defaultActionForKey:方法。

为什么UIView关联的CALayer隐式动画被禁用呢?
UIView对它关联的CALayer都扮演了一个委托的角色,并实现了 actionForLayer:forKey:方法。当不在一个动画块的实现中,其返回nil,此时将不会有动画发生;在动画块的范围之内,则返回一个非空值。如下:

//test layer action when outside of animation block 
NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);

//begin animation block 
[UIView beginAnimations:nil context:nil];

//test layer action when inside of animation block 
NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);

//end animation block 
[UIView commitAnimations];

//output:
$ LayerTest[21215:c07] Outside:  
$ LayerTest[21215:c07] Inside: 

另外禁用隐式动画的方法:

  • setDisableActions: 用来对所有属性打开或关闭隐式动画。
[CATransaction setDisableActions:YES];

通过设置actions字典自定义动画行为

//create sublayer 
self.colorLayer = [CALayer layer]; 
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;

//add a custom action 
CATransition *transition = [CATransition animation]; 
transition.type = kCATransitionPush; 
transition.subtype = kCATransitionFromLeft; 
self.colorLayer.actions = @{@"backgroundColor": transition};

//add it to our view 
[self.layerView.layer addSublayer:self.colorLayer];

显式动画

对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动,这样的动画叫做显示动画。显示动画分类与继承关系如下:


iOS核心动画总结_第6张图片
显式动画.png

属性动画 CAPropertyAnimation

首先我们来探讨一下属性动画。属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础动画和关键帧动画。

iOS核心动画总结_第7张图片
常用动画属性
基础动画 CABasicAnimation

动画其实就是一段时间内发生的改变,最简单的形式就是从一个值改变到另一个值,这也是CABasicAnimation最主要的功能。

CAPropertyAnimation通过指定动画的keyPath作用于一个单一属性,CAAnimation通常应用于一个指定的CALayer,于是这里指的也就是一个图层的keyPath了。实际上它是一个关键路径(一些用点表示法可以在层级关系中指向任意嵌套的对象),而不仅仅是一个属性的名称,因为这意味着动画不仅可以作用于图层本身的属性,而且还包含了它的子成员的属性,甚至是一些虚拟的属性(后面会详细解释)。
CABasicAnimation继承于CAPropertyAnimation,并添加了如下属性:

id fromValue 
id toValue 
id byValue

从命名就可以得到很好的解释:fromValue代表了动画开始之前属性的值,toValue代表了动画结束之后的值,byValue代表了动画执行过程中改变的值。
fromValue,toValue和byValue属性可以用很多种方式来组合,但为了防止冲突,不能一次性同时指定这三个值。例如,如果指定了fromValue等于2,toValue等于4,byValue等于3,那么Core Animation就不知道结果到底是4(toValue)还是5(fromValue + byValue)了。他们的用法在CABasicAnimation头文件中已经描述的很清楚了,所以在这里就不重复了。总的说来,就是只需要指定toValue或者byValue,剩下的值都可以通过上下文自动计算出来。

下面我们通过CABasicAnimation来改变图层的位置:

    CABasicAnimation *anim = [CABasicAnimation animation];
    // CABasicAnimation属性
    anim.toValue = @400;
    // 父类CAPropertyAnimation属性
    anim.keyPath = @"position.y";
    // 基类CAAnimation
    // 基类CAAnimation 结束后不移除动画
    anim.removedOnCompletion = NO;
    // 协议CAMediaTiming添加的属性 kCAFillModeForwards保持动画最后的状态
    anim.fillMode = kCAFillModeForwards;
    anim.delegate = self;
    [_greenView.layer addAnimation:anim forKey:nil];

这里需要注意的是必须将动画的属性removedOnCompletion设置为NO,即不移除动画,fillMode设置为kCAFillModeForwards,即保持最后的状态,否则动画完后会回复到之前的状态。

我们给greenView添加手势,如下:

UITapGestureRecognizer *greenViewGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(greenViewGesClick:)];
    [self.greenView addGestureRecognizer:greenViewGes]; 
- (void)greenViewGesClick:(UITapGestureRecognizer *)greenViewGes{
    NSLog(@"触摸了greenView");
}

当动画完毕后我们去点击图层在界面上呈现的位置,会发现没有反应,反而在点击图层原来的位置上是,手势触摸有反应。


iOS核心动画总结_第8张图片
动画录屏.gif

这是为什么呢?这就涉及到CALayer的呈现层和模型层的知识了。

呈现层和模型层(presentationLayer和modelLayer)

CALayer有两个图层,一个是modelLayer(存储和读取),代表layer实际的坐标值,一个是presentationLayer,代表了layer做动画时实际呈现的位置。上述动画中,_greenView.layer虽然做动画到了别的位置,但实际位置并未改变,所以我们在点击动画的位置没有反应,而点击原来的位置反而有反应。

如果想让你做动画的图层相应用户的输入,可以使用-hitTest:来判断指定的图层是否被触摸.通过判断[_greenView.layer.presentationLayer hitTest:point])是否有值来判断触摸点是否在presentationLayer上。

关键帧动画

和CABasicAnimation类似,CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类,CAKeyframeAnimation同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。

关键帧起源于传动动画,意思是指主导的动画在显著改变发⽣时 重绘当前帧(也就是关键帧),每帧之间剩下的绘制(可以通过 关键帧推算出)将由熟练的艺术家来完成。

CAKeyframeAnimation也是同样的道理:你提供了显著的帧,然后Core Animation在每帧之间进行插入。

示例如下:

    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"transform.rotation";
    anim.repeatDuration = MAXFLOAT;
    // 和下面三句代码等价
//    anim.values = @[@angleToRadians(-6),@angleToRadians(6),@angleToRadians(-6)];
    anim.values = @[@angleToRadians(-6),@angleToRadians(6)];
    // 动画反转
    anim.autoreverses = YES;
    // 加速2倍
    anim.speed = 2;
    [self.iconView.layer addAnimation:anim forKey:nil];
iOS核心动画总结_第9张图片
关键帧动画1

也可以通过CGPath指定动画路径。

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 200)];
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];

    // 绘制小车的layer
    CALayer *carLayer = [CALayer layer];
    carLayer.contents = (__bridge id)[UIImage imageNamed:@"car.png"].CGImage;
    carLayer.frame = CGRectMake(50-36, 200-36, 36, 36);
    carLayer.anchorPoint = CGPointMake(0.5, 0.8);
    [self.view.layer addSublayer:carLayer];

    // 绘制曲线的layer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = nil;
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:shapeLayer];

    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"position";
    // 设置曲线
    anim.path = path.CGPath;
    anim.duration = 4.0f;
    anim.removedOnCompletion = NO;
    anim.fillMode = kCAFillModeForwards;
    // 跟着曲线的方向移动
    anim.rotationMode = kCAAnimationRotateAuto;
    [carLayer addAnimation:anim forKey:nil];
    
iOS核心动画总结_第10张图片
关键帧动画2.gif
补充 CAShapeLayer

CAShapeLayer 是一个通过矢量图形而不是 bitmap 来绘制的图层子类。通过制定诸如颜色和线宽等属性,用 CGPath 来定义想要绘制的图形。
相比⽤Core Graphics直接向原始的CALayer 的内容中绘制路径,其优点如下:

  • 渲染快速。其使用了硬件加速,绘制同一图形比 Core Craphics 快得多。
  • 高效使用内存。不需要向 CALayer 一样创建一个寄宿图,无论多大,也不会占用太多的内存。
  • 不会被图层边界裁掉。⼀个 CAShapeLayer 可以在边界之 外绘制
  • 不会出现像素化。当对 CAShapeLayer 做3D变换时,它不像⼀个有寄宿图的普通图层⼀样变得像素化。

动画组 CAAnimationGroup

CAAnimationGroup, 是另⼀个继承于 CAAnimation 的⼦类, 它添加了⼀个 animations 数组的属性,⽤来组合别的动画。示例如下:

//    曲线
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 200)];
    [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(180, 100) controlPoint2:CGPointMake(200, 300)];
    //需要添加在layer上
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor = nil;
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:shapeLayer];

    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 60, 60);
    colorLayer.position = CGPointMake(50, 200);
    colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:colorLayer];

    //    过山车的动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"position";
    anim.path = path.CGPath;
    //    anim.duration = 4.0f;
    //    anim.removedOnCompletion = NO;
    //    anim.fillMode = kCAFillModeForwards;
    //    anim.rotationMode = kCAAnimationRotateAuto;
    //    [colorLayer addAnimation:anim forKey:nil];
    //    改变大小
    CABasicAnimation *sizeAnim = [CABasicAnimation animation];
    sizeAnim.keyPath = @"transform.scale";
    sizeAnim.toValue = @.5;
    //    sizeAnim.duration = 4.0;
    //    sizeAnim.fillMode = kCAFillModeForwards;
    //    sizeAnim.removedOnCompletion = NO;
    //
    //    [colorLayer addAnimation:sizeAnim forKey:nil];

    //    修改颜色
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1];

    CABasicAnimation *colorAnim = [CABasicAnimation animation];
    colorAnim.keyPath = @"backgroundColor";
    colorAnim.toValue = (id)color.CGColor;
    //    colorAnim.duration = 4.0f;
    //    colorAnim.fillMode = kCAFillModeForwards;
    //    colorAnim.removedOnCompletion = NO;
    //    [colorLayer addAnimation:colorAnim forKey:nil];

    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = @[anim, sizeAnim, colorAnim];
    group.duration = 4.0f;
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    [colorLayer addAnimation:group forKey:nil];

效果如下:

iOS核心动画总结_第11张图片
动画组.gif

转场动画 CATransition

CATransition,对不可动画属性或整个图层做动画处理。

转场动画并不像属性动画那么平滑的在两个值之间做动画,而是影响到整个图层的变化。转场动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。

为了创建一个转场动画,我们使用CATransition,同样是一个CAAnimation的子类,和别的子类不同,CAAnimation有一个type和subtype来标识变换效果。

type,NSString类型,公开的值有如下四种:

  • kCATransitionFade,新图层键入,默认值。
  • kCATransitionMoveIn,新图层顶部滑动进入。
  • kCATransitionPush,新图层把老图层退总,自己进入。
  • kCATransitionReveal,原始图片划出来显示新的图层。

除此之外还有些系统未公开的转场动画类型,如下图:


iOS核心动画总结_第12张图片
转场动画类型

subtype, NSString 类型,值如下:

  • kCATransitionFromRight
  • kCATransitionFromLeft
  • kCATransitionFromTop
  • kCATransitionFromBottom

示例如下,对图片替换加入转换效果:

    NSString *imgName = _imgs[_index];
    _imageView.image = [UIImage imageNamed:imgName];
    CATransition *anim = [CATransition animation];
    anim.type = @"suckEffect";
    //anim.type = kCATransitionPush;
    anim.subtype = kCATransitionFromLeft;
    anim.duration = .5;
    //    anim.startProgress = .2;
    //    anim.endProgress = .5;
    [_imageView.layer addAnimation:anim forKey:nil];

动画效果如下:

iOS核心动画总结_第13张图片
转场动画.gif

动画过程中取消动画

移除某一属性的动画:

  • (void)removeAnimationForKey:(NSString *)key;

或者移除所有动画:

  • (void)removeAllAnimations;

动画⼀旦被移除,图层的外观就⽴刻更新到当前的模型图层的值。⼀般说来,动画在结束之后被⾃动移除,除⾮设置 removedOnCompletion 为 NO ,如果你设置动画在结束之后不被⾃动移除,那么当它不需要的时候你要⼿动移除它;否则它会⼀直存在于内存中,直到图层被销毁。

CAMediaTiming 协议

定义了一段动画内用来控制逝去时间的属性的集合, CALayer 和 CAAnimation 都实现了这个协议,所以时间可以被任意基于图层或动画的类控制。

  • beginTime,开始之前的延迟时间
  • timeOffset,时间偏移值
  • repeatCount,动画重复次数, 不可和 repeatDuration 同时使用
  • repeatDuration,动画重复总时间,不可和 repeatCount 同时使用
  • duration,CFTimeInterval, 动画时长
  • speed,动画速度,默认1.0,取值0~n,一个时间的倍数, 当等于0时相当于暂停。
  • autoreverses,动画完成后是否反向动画到原始值
  • fillMode,动画完成后,动画值的填充模式
    • kCAFillModeForwards,动画完成后保持动画结束状态
    • kCAFillModeBackwards,动画开始前立即进入动画初始状态
    • kCAFillModeBoth, kCAFillModeForwards | kCAFillModeBackwards
    • kCAFillModeRemoved,动画完车后移除动画结束状态
iOS核心动画总结_第14张图片
CAMediaTiming.png

动画速度

CAMediaTimingFunction , Core Animation 内嵌的一系列标准函数:

  • kCAMediaTimingFunctionLinear
  • kCAMediaTimingFunctionEaseIn 慢慢加速,全速停止
  • kCAMediaTimingFunctionEaseOut 全速开始,慢慢减速停止
  • kCAMediaTimingFunctionEaseInEaseOut
  • kCAMediaTimingFunctionDefault ,类似kCAMediaTimingFunctionEaseInEaseOut,但缓冲速度略慢

通过 +setAnimationTimingFunction 或 +timingFUnctionWithName 来使用。

iOS核心动画总结_第15张图片
动画缓冲曲线.png
自定义缓冲函数

通过+functionWithControlPoints::::来使用:

[CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];

文章中Demo地址:https://github.com/huangmoxianganquan/CoreAnimationExample
参考文章:
1、核心动画一览
2、CoreAnimation4-隐式动画和显式动画

你可能感兴趣的:(iOS核心动画总结)