CAAnimation核心动画

书籍是人类进步的阶梯

总览思维导图

CAAnimation核心动画_第1张图片
核心动画思维导图.png

一、图层树

1.1.contents

  • 简介:(id类型),虽然是id类型但如果给contents赋的不是CGImage,那么得到的图层将是空白的。
    layer.contents = (__bridge id)image.CGImage;

  • 注意:在加载图片时为了适应视图,我们一般这么处理:
    view.contentMode = UIViewContentModeScaleAspectFit;
    但在CALayer与contentMode对应的属性叫做contentsGravity
    self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

1.2 contentsScale

  • 寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数:当用代码来处理寄宿图的时候,一定要记住要手动的设置图层的contentsScale属性,否则,你的图片在Retina设备上就显示得不正确啦。代码如下:
    layer.contentsScale = [UIScreen mainScreen].scale;

1.3 maskToBounds

  • UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds

1.4 contentsRect

  • 默认的contentsRect是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果设置{0,0,0.5,0.5},那只显示左上角部分(整体的1/4部分)
CAAnimation核心动画_第2张图片
contentsRect

1.5 contentsCenter

  • 其实是一个CGRect,它定义了一个固定的边框和一个
    在图层上可拉伸的区域。默认情况下,contentsCenter是{0, 0, 1, 1},这意
    味着如果大小(由conttensGravity决定)改变了,那么寄宿图将会均匀地拉
    伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造
    一个边框。图2.9展示了contentsCenter设置为{0.25, 0.25, 0.5, 0.5}的效果。


    CAAnimation核心动画_第3张图片
    contentsCenter.png

1.6 -drawRect:方法:

  • 当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用;
  • 调用了-setNeedsDisplay方法时,-drawRect:方法会被调用;

二、图层几何学

2.1 frame、bounds、position

  • 当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽高可能和bounds的宽高不再一致了


    CAAnimation核心动画_第4张图片
    frame

2.2 锚点anchorPoint

  • anchorPoint是用来移动图层的把柄。anchorPoint用单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}

2.3zPosition

  • 在大多数情况下其实并不常用。zPosition最实用的功能就是改变图层的显示顺序了。通过增加图层的zPosition,就可以把图层向相机方向前置,于是它就在所有其他图层的前面了(或者至少是小于它的zPosition值的图层的前面)。

2.4 -containsPoint

  • 接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint point = [[touches anyObject] locationInView:self.view];
    point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
    //get layer using containsPoint:
    if ([self.layerView.layer containsPoint:point]) {
        //convert point to blueLayer’s coordinates
        point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
        if ([self.blueLayer containsPoint:point]) {
          // your code
        } else {
           // your other code
        }
    }
}

2.5 -hitTest

  • 方法接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //get touch position
    CGPoint point = [[touches anyObject] locationInView:self.view];
    //get touched layer
    CALayer *layer = [self.layerView.layer hitTest:point];
    //get layer using hitTest
    if (layer == self.blueLayer) {
       // your code
    } else if (layer == self.layerView.layer) {
        // your other code
    }
}

三、专用图层CALayer

3.1 CAShapeLayer

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

1.CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。
2.CAShapeLayer属性是CGPathRef类型

  • 绘制圆角:( 需求:三个圆角,一个直角)
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

3.2 CATextLayer

  • CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。
  • CATextLayer使用了Core text,并且渲染得非常快。可以尝试封装一个CATextLayer用于替换UILabel

UILabel的替代品

  • 使用CATextLayer来封装一个UILabel的子类
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass
{
  //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
  return [CATextLayer class];
}

- (CATextLayer *)textLayer
{
  return (CATextLayer *)self.layer;
}

- (void)setUp
{
  //set defaults from UILabel settings
  self.text = self.text;
  self.textColor = self.textColor;
  self.font = self.font;
//we should really derive these from the UILabel settings too
  //but that's complicated, so for now we'll just hard-code them
  [self textLayer].alignmentMode = kCAAlignmentJustified;
  
  [self textLayer].wrapped = YES;
  [self.layer display];
}

- (id)initWithFrame:(CGRect)frame
{
  //called when creating label programmatically
  if (self = [super initWithFrame:frame]) {
    [self setUp];
  }
  return self;
}

- (void)awakeFromNib
{
  //called when creating label using Interface Builder
  [self setUp];
}
- (void)setText:(NSString *)text
{
  super.text = text;
  //set layer text
  [self textLayer].string = text;
}

- (void)setTextColor:(UIColor *)textColor
{
  super.textColor = textColor;
  //set layer text color
  [self textLayer].foregroundColor = textColor.CGColor;
}

- (void)setFont:(UIFont *)font
{
  super.font = font;
  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  [self textLayer].font = fontRef;
  [self textLayer].fontSize = font.pointSize;
  CGFontRelease(fontRef);
}
@end

3.3 CATransformLayer

  • CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构

3.4 CATiledLayer

  • CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入

3.5 CAGradientLayer

  • CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。

3.6 CAReplicatorLayer:(反射效果)

  • CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。

3.7 CAEmitterLayer(粒子、火焰特效)

  • 简介CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
  • CAEmitterCell
    CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage。

CAEMitterCell的属性

  • 这种粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。
  • 例子某一属性的变化范围。比如emissionRange属性的值是2π,这意味着例子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形。
  • 指定值在时间线上的变化。比如,在示例中,我们将alphaSpeed设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐消失的效果。
  • preservesDepth:是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层。
  • renderMode:控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为kCAEmitterLayerAdditive,它实现了这样一个效果:合并例子重叠部分的亮度使得看上去更亮。
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    CAEmitterLayer *emitter = [CAEmitterLayer layer];
    emitter.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:emitter];

    //configure emitter
     emitter.renderMode = kCAEmitterLayerAdditive;
    emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);

    //create a particle template
    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
    cell.birthRate = 150;
    cell.lifetime = 5.0;
    cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
    cell.alphaSpeed = -0.4;
    cell.velocity = 50;
    cell.velocityRange = 50;
    cell.emissionRange = M_PI * 2.0;

    //add particle template to emitter
    emitter.emitterCells = @[cell];
}
@end

3.8 CAEAGLLayer

  • 用来显示任意的OpenGL图形,一般用不到。

3.9 AVPlayerLayer

  • AVPlayerLayer是CALayer的子类,它继承了父类的所有特性,主要用于视频播放。

4.0 CAScrollLayer

  • UIView中的UIScrollView的底层封装。

四、视觉效果

4.1 圆角

  • conrnerRadius:控制着图层角的曲率。(只影响背景颜色而不影响背景图片或是子图层),一般和masksToBounds配合着使用。
  • borderWidth:定义边框粗细
  • borderColor:边框的颜色

4.2 阴影

  • shadowOpacity:0.0(不可见)和1.0(完全不透明)之间的浮点数;
  • shadowColor:控制阴影的颜色;
  • shadowOffset:控制阴影的方向和距离。默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移;
  • shadowRadius:控制着阴影的模糊度,当值为0时,阴影和视图有非常确定的边界线。值越大,边界线看上去就会越来越模糊和自然;
  • shadowPath:一个CGPathRef类型(一个指向CGPath的指针)。我们可以通过这个属性单独于图层形状之外指定阴影的形状;
  • mask:mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。代码演示:
@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //create mask layer
  CALayer *maskLayer = [CALayer layer];
  maskLayer.frame = self.layerView.bounds;
  UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
  maskLayer.contents = (__bridge id)maskImage.CGImage;

  //apply mask to image layer
  self.imageView.layer.mask = maskLayer;
}
@end

效果:


CAAnimation核心动画_第5张图片
mask

4.3拉伸过滤:

view.layer.magnificationFilter = kCAFilterNearest;

4.4 组透明

  • 透明度会叠加,即当一个控件有子控件时,设置父控件的透明度(UIView对应alpha、CALayer对应opacity),子控件的透明度也会被影响。设置CALayer的一个叫做shouldRasterize属性来实现组透明的效果,如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了:
    view.layer.shouldRasterize = YES;
    view.layer.rasterizationScale = [UIScreen mainScreen].scale;

五、变换

5.1 仿射变换CGAffineTransform(2D变换)

5.1.1 原理:
1. UIView的transform属性是一个CGAffineTransform类型,用于在二维空间做旋转,缩放和平移;
2. CALayer对应于UIView的transform属性叫做affineTransform;
3. CALayer同样也有一个transform属性,但它的类型是CATransform3D。
5.1.2 主要方法:
CGAffineTransformMakeRotation(CGFloat angle);                    //旋转
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);           //缩放
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty);  //平移

5.1.3 混合变换:CGAffineTransformIdentity

// 需求:先缩小50%,再旋转30度,最后向右移动200个像素
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a new transform
    CGAffineTransform transform = CGAffineTransformIdentity; 
    //scale by 50%
    transform = CGAffineTransformScale(transform, 0.5, 0.5);
    //rotate by 30 degrees
    transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
    //translate by 200 points
    transform = CGAffineTransformTranslate(transform, 200, 0);
    //apply transform to layer
    self.layerView.layer.affineTransform = transform;
}

3D变换CATransform3D

5.2.1 主要方法:
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z);  //旋转
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz);  //缩放
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz);  //平移

5.2.2 透视投影:m34

m34的默认值是0,我们可以通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,通常500-1000就已经很好了。

5.2.3 灭点

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点(在现实中,这个点通常是视图的中心)

  • sublayerTransform:它也是CATransform3D类型,它会影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法:
@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //apply perspective transform to container
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = - 1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //rotate layerView1 by 45 degrees along the Y axis
    CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView1.layer.transform = transform1;
    //rotate layerView2 by 45 degrees along the Y axis
    CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
    self.layerView2.layer.transform = transform2;
}

六、隐式动画

事务(CATransaction)

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

主要用法:

  [CATransaction begin]; // 入栈
  [CATransaction commit]; //出栈
  +setAnimationDuration: //设置当前事务的动画时间
  +animationDuration // 获取值(默认0.25秒)
  [CATransaction setDisableActions:YES]; //对所有属性关闭隐式动画

隐式动画如何实现:

  • 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
  • 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
  • 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
  • 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。

所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。

七、显式动画

7.1 关键帧动画(CAKeyframeAnimation)

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

  • 关键帧起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就是关键帧),每帧之间剩下的绘制(可以通过关键帧推算出)将由熟练的艺术家来完成。CAKeyframeAnimation也是同样的道理:你提供了显著的帧,然后Core Animation在每帧之间进行插入。
代码:
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    ...
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    // 平移动画
    animation.keyPath = @"position";
    //持续时间
    animation.duration = 4.0;
    // 动画路径
    animation.path = bezierPath.CGPath;
    // *图层将会根据曲线的切线自动旋转*
    animation.rotationMode = kCAAnimationRotateAuto;
    [shipLayer addAnimation:animation forKey:nil];
}

八、动画组CAAnimationGroup

  • CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性,用来组合别的动画。实例代码:
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
    //draw the path using a CAShapeLayer
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    //add a colored layer
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 64, 64);
    colorLayer.position = CGPointMake(0, 150);
    colorLayer.backgroundColor = [UIColor greenColor].CGColor;
    [self.containerView.layer addSublayer:colorLayer];
//create the position animation
    CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
    animation1.keyPath = @"position";
    animation1.path = bezierPath.CGPath;
    animation1.rotationMode = kCAAnimationRotateAuto;
    //create the color animation
    CABasicAnimation *animation2 = [CABasicAnimation animation];
    animation2.keyPath = @"backgroundColor";
    animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
    //create group animation
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[animation1, animation2]; 
    groupAnimation.duration = 4.0;
    //add the animation to the color layer
    [colorLayer addAnimation:groupAnimation forKey:nil];
}

九、CATransition(过渡动画)

  • type(动画类型)

    • kCATransitionFade (淡入淡出)
    • kCATransitionMoveIn(从顶部滑动进入)
    • kCATransitionPush
    • kCATransitionReveal
  • subtype(动画方向)

    • kCATransitionFromRight
    • kCATransitionFromLeft
    • kCATransitionFromTop
    • kCATransitionFromBottom
  • 在动画过程中取消动画

- (void)removeAnimationForKey:(NSString *)key; // 移除某个动画
- (void)removeAllAnimations; // 移除所有动画

十、CAMediaTiming协议

10.1 概念

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

10.2 属性

  • duration:CFTimeInterval的类型,对将要进行的动画的一次迭代指定了时间;
  • repeatCount:动画重复的迭代次数;
  • repeatDuration:动画重复一个指定的时间,而不是指定次数;
  • autoreverses:(BOOL类型)在每次间隔交替循环过程中自动回放。

10.3 注意

  • duration和repeatCount默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅代表了“默认”,也就是0.25秒和1次;
  • 把repeatDuration设置为INFINITY,于是动画无限循环播放,设置repeatCount为INFINITY也有同样的效果。
  • repeatCount和repeatDuration可能会相互冲突,所以你只要对其中一个指定非零值.

10.4 相对时间

  • beginTime:指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)
  • speed:是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了.
  • timeOffset:增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始.

基于定时器的动画

NSTimer并不准确的原因:

iOS上的每个线程都管理了一个NSRunloop,通过一个循环来完成一些任务列表。当你设置一个NSTimer,他会被插入到当前任务列表中,然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久才完成就会导致延迟很长一段时间.

性能优化(待完善)

cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

你可能感兴趣的:(CAAnimation核心动画)