Objective-C的CALayer学习笔记

CALayer简介

CALayer(图层)通常用于存储和管理UIView(视图)的视觉内容(比如背景颜色、边框和阴影),创建的UIView会持有一个从不为nil的layer属性,可以用来访问UIView的CALayer,也就是UIView显示的视觉内容实际上是有CALayer来呈现的。但由于CALayer不能与用户进行交互,但是UIView可以,所以这两个对象更像明确的划分了功能,CALayer呈现视觉内容,UIView响应事件。

CALayer除了管理呈现的视觉内容之外,还维护用于在屏幕上显示的视觉内容的相关的几何形状信息(如position(位置)、大小(size)和转换(transform))。可以使用修改CALayer的视觉内容相关属性或几何形状信息相关属性来启动动画,CALayer对象通过定义CALayer计时信息的CAMediaTiming协议封装CALayer及其动画的持续时间和速度。

如果CALayer对象是由UIView创建的,则UIView通常会自动将自身指定为CALayer的代理,并且不应更改该关系。对于自己创建的在没有视图的情况下用于显示内容CALayer对象,可以为其指定代理对象。

CALayer的常用属性
@property CGRect bounds;

属性描述CALayer对象的边界,默认为CGRectZero,可设置动画。

@property CGPoint position;

属性描述CALayer对象边界矩形相对其父级层(superlayer)的定位点位置。默认为CGPointZero。可设置动画。

@property CGPoint anchorPoint;

属性描述定位CALayer对象边界矩形的锚点,可以做成动画。使用单位坐标空间指定此属性的值。此属性的默认值为(0.5,0.5),它表示CALayer(图层)对象边界矩形的中心。视图的所有几何操作都发生在指定的点上,例如对具有默认锚点的图层应用旋转变换会导致该图层绕其中心旋转,将锚点更改为一个不同的位置将导致图层围绕新点旋转。

@property CGFloat zPosition;

属性描述CALayer对象在Z轴上的位置,可设置动画,此属性的默认值为0。更改此属性的值将更改屏幕上各CALayer(图层)对象的前到后顺序。与值较低的层相比,值较高的层在视觉上更靠近查看器。这会影响框架矩形重叠的CALayer(图层)对象的可见性。

\color{red}{zPosition使用示例,例如:要让表视图的某一cell这样显示:}

cell = [tableView dequeueReusableCellWithIdentifier:YSCUserCenterTitleCellReuseIdentifier];
cell.layer.zPosition = 10;
Jietu20191127-212056@2x.gif

可以让上部的cell中设置背景的视图底部约束超出cell,但在没有设置zPosition属性时,可能是这样的:

Jietu20191127-212804@2x.gif
@property(getter=isHidden) BOOL hidden;

属性描述隐藏CALayer对象及其子层(sublayer),如果为true,则不显示CALayer对象及其子层(sublayer)。默认为“ NO”,可设置动画。

@property(nullable, readonly) CALayer *superlayer;

属性描述调用方的superlayer(父级层)对象。隐式更改以匹配“sublayers”属性描述的层次结构。

@property BOOL masksToBounds;

属性描述 : 一个布尔值,指示CALayer对象的子层(sublayers)超出边界的部分是否将剪裁,可设置动画。

@property(nullable, strong) id contents;

属性描述提供图层内容的对象,可设置动画,此属性的默认值为nil。如果使用CALayer对象来显示静态图像,可以将此属性设置为包含想要显示的图像的CGImageRef对象。如果CALayer对象绑定到一个视图对象,则应该避免直接设置这个属性的内容,因为视图和图层之间的相互作用通常会导致视图在随后的更新中替换该属性的内容。

@property(copy) CALayerContentsGravity contentsGravity;

属性描述 :一个常量,指定CALayer(图层)对象的内容在其边界内的定位或缩放方式,此属性的默认值为kCAGravityResize。

\color{red}{例如 :给图层设置一张静态图片:}

CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 150, 200, 200);
layer.contents = (id)[UIImage imageNamed:@"PokemonV"].CGImage;
layer.contentsGravity = kCAGravityResize;
[self.view.layer addSublayer:layer];
截屏2022-07-06 13.39.00.png
@property CGRect contentsRect;

属性描述 :定义了将要绘制到图层中的contents属性的子矩形,默认为单位矩形(0.0, 0.0, 1.0, 1.0),可以动画。如果请求单元矩形外的像素,则contents图像的边缘像素将向外扩展。如果提供了一个空矩形,则结果是未定义的。

例如 : layer.contentsRect = CGRectMake(0.0, 0.0, 0.5, 0.5)时:

截屏2022-07-06 13.41.18.png

layer.contentsRect = CGRectMake(0.0, 0.0, 1.5, 1.5)时:

截屏2022-07-06 13.42.15.png
@property CGFloat cornerRadius;

属性描述绘制CALayer对象背景圆角时使用的半径,可设置动画。将半径设置为大于0.0的值会导致图层开始在其背景上绘制圆角,默认情况下,角半径不应用于层的contents属性中的图像,它只适用于图层的背景颜色和边框,但是如果将masksToBounds属性设置为YES,会导致contents属性中的图像剪切到圆角。

@property CGFloat borderWidth;

属性描述CALayer对象边框的宽,可以做成动画。当该值大于0.0时,根据此属性中指定的值在调用方边界插入属性中指定的值来绘制边框,使用当前的borderColor值设置颜色,并最终在调用方的内容和子层之上合成,并包括cornerRadius特性的影响。此属性的默认值为0.0。

@property(nullable) CGColorRef borderColor;

属性描述CALayer对象边框的颜色,可以做成动画,此属性的默认值是不透明的黑色。

@property float shadowOpacity;

属性描述CALayer对象阴影的不透明度,可以做成动画,此属性中的值必须在0.0(透明)到1.0(不透明)的范围内,这个属性的默认值是0.0。

@property(nullable) CGColorRef shadowColor;

属性描述CALayer对象阴影的颜色,可以做成动画,此属性的默认值是不透明的黑色。

@property CGSize shadowOffset;

属性描述CALayer对象阴影阴影的偏移(以点为单位),这个属性的默认值是(0.0,-3.0),宽度为负时阴影左移,宽度为正时阴影右移,高度为负时阴影上移,高度为正时阴影下移,可以做成动画。

@property CGFloat shadowRadius;

属性描述用于渲染CALayer对象阴影的模糊半径(以点为单位),这个属性的默认值是3.0,可以做成动画。

@property(nullable) CGPathRef shadowPath;

属性描述CALayer(图层)对象阴影的形状,该属性的默认值为nil,这将导致该CALayer(图层)对象使用标准的阴影形状。如果为这个属性指定了一个值,图层会使用指定的路径来创建它的阴影轮廓,并使用非零缠绕规则(图形学中判断一个点是否在自相交的多边形内部或外部的规则)结合当前阴影颜色、不透明度和模糊半径填充创建的阴影形状。与大多数可动画属性不同,这个属性(与所有CGPathRef可动画属性一样)不支持隐式动画,但是路径对象可以使用CAPropertyAnimation的任何具体子类进行动画。

\color{red}{例如 :添加圆形的阴影:}

CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 150, 200, 200);
layer.contents = (id)[UIImage imageNamed:@"PokemonV"].CGImage;
layer.contentsGravity = kCAGravityResize;
layer.cornerRadius = 10.0;
layer.shadowOpacity = 0.5f;
layer.shadowOffset = CGSizeMake(-50, -50);
layer.shadowColor = [UIColor redColor].CGColor;
layer.shadowRadius = 10.0f;
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 300, 300)];
layer.shadowPath = path.CGPath;
[self.view.layer addSublayer:layer];
截屏2022-07-08 09.14.07.png
@property(nullable) CGColorRef backgroundColor;

属性描述CALayer对象的背景颜色,此属性的默认值为nil,可以做成动画。

@property CGFloat rasterizationScale;

属性描述相对于图层的坐标空间,光栅化内容的比例。可设置动画

@property BOOL shouldRasterize;

属性描述光栅化,指示layer在合成之前是否被渲染为位图的布尔值,可动画。启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像,然后这个图像将会被缓存起来。

如果有很多的子图层或者有复杂的效果应用,开启shouldRasterize就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。所以需要根据实际情况取舍。单次使用或者视图有变动,shouldRasterize不会有任何用途,反而会牺牲内存。对一个层级复杂的视图做动画时,可以启用shouldRasterize避免GPU每帧都重新合成。

调试方法:

xcode开发工具提供的常用的测量方式:

Xcode->Debug->View Debuging->Rendering->Color Offscrenn-rended Yellow可以为触发了离屏渲染的layer着黄色。

Xcode->Debug->View Debuging->Rendering->Hits Green and Misses Red
这个指标可以反映我们通过尝试开启shouldRasterize提升性能的时候是不是达到了预期效果。

当使用shouldRasterize属性的时候,耗时的图层绘制会被缓存,然后当做一个简单的扁平图片呈现。当缓存再生的时候这个选项就用红色对栅格化图层进行了高亮,缓存被重复使用的话就会以绿色进行高亮。如果缓存频繁再生的话(红色太多),就意味着栅格化可能会有负面的性能影响了。

CALayer的常用函数
- (void)addSublayer:(CALayer *)layer;

函数描述将参数给定的CALayer对象添加到调用方的子层(sublayers)列表中。如果sublayers属性中的数组为nil,调用此方法将为sublayers属性创建一个数组,并将指定的图层添加到该属性中。

参数 :

layer : 要添加的CALayer对象。

- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx;

函数描述 :将参数给定的CALayer对象插入到调用方的子层列表中指定索引处的位置

参数 :

layer : 要插入到当前调用方图层层次结构中的子图层对象。

idx : 插入图层的索引,该值必须是子层数组中有效的索引。

- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling;

函数描述 :将参数给定的CALayer对象插入到调用方子层列表中指定的图层之下

参数 :

layer :要插入到当前调用方图层层次结构中的子图层对象。

sibling : 调用方指定的图层。如果指定的图层不在调用方的子层数组中,此方法将引发异常。

- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling;

函数描述 :将参数给定的CALayer对象插入到调用方子层列表中指定的图层之上

参数 :

layer :要插入到当前调用方图层层次结构中的子图层对象。

sibling : 调用方指定的图层。如果指定的图层不在调用方的子层数组中,此方法将引发异常。

- (void)replaceSublayer:(CALayer *)oldLayer with:(CALayer *)newLayer;

函数描述 :使用新的CALayer对象替换调用方指定的子图层

参数 :

oldLayer : 调用方要被替换的子图层。

newLayer : 用来替换oldLayer的图层对象。

- (void)removeFromSuperlayer;

函数描述将CALayer对象在其父级层(superlayer)移除。可以使用此方法从图层的层次结构中删除一个层(及其所有子层)。此方法更新父级层(superlayer)的子层列表(sublayers),并将该CALayer对象的父级层(superlayer)属性设置为nil。

CALayer关于动画的常用函数
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;

函数描述在调用函数的CALayer对象上附加一个动画对象。通常这是通过CAAnimation对象的操作隐式调用的。

“key”可以是任何字符串,这样每个唯一的key在每个CALayer对象上只添加一个动画。特殊的key值“transition”是自动用于过渡动画,并且nil指针也是一个有效的键。

如果动画的“duration”属性为0或负值,则会给它默认的持续时间,否则为“animationDuration”事务属性的值或0.25秒。

动画在被添加到图层之前会被复制,所以任何对“anim”的后续修改都不会有影响,除非它被添加到另一个图层。

参数 :

anim : 要添加到渲染树的动画。此对象由渲染树复制,而不是引用。因此,对对象的后续修改不会传播到渲染树中。

key : 标识动画的字符串,每一个唯一的key只有一个动画被添加到调用该函数的图层。特殊的key值“kCATransition”自动用于过渡动画,也可以为该参数指定nil。

- (void)removeAllAnimations;

函数描述 :调用该函数的CALayer对象上所附加的全部动画都将被删除。

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

函数描述 : 在调用该函数的CALayer对象上移除使用指定的key标识的动画对象

参数 :

key : 要移除的动画的标识符。

CAGradientLayer - 颜色渐变层

CAGradientLayer继承CALayer,是可以在其背景色上绘制颜色渐变的图层,默认情况下颜色均匀地分布在整个图层中,但可以通过选择指定的渐变位置来控制颜色的位置。

CAGradientLayer常用属性
@property CGPoint startPoint;

函数描述在图层的坐标空间中绘制渐变时的起点,可设置动画。起点对应于梯度的第一个停止点,该点在单位坐标空间中定义,然后在绘制时映射到层的边界矩形。默认值为(0.5,0.0)。

@property CGPoint endPoint;

函数描述在图层的坐标空间中绘制渐变时的终点,可设置动画。终点对应于渐变的最后一个停止点,该点在单位坐标空间中定义,然后在绘制时映射到层的边界矩形。默认值为(0.5,1.0)。

@property(nullable, copy) NSArray *colors;

函数描述:CGColorRef对象的数组,定义每个渐变停止点的颜色。数组中只设置一个颜色是不显示的。可设置动画。

@property(copy) CAGradientLayerType type;

函数描述图层绘制渐变时的样式。默认值为kCAGradientLayerAxial。

  • CAGradientLayerType列举的样式:
// 轴向梯度(也称为线性梯度)沿两个定义端点之间的轴变化,垂直于这条轴线上的线条中所有点都具有相同的颜色值。
CA_EXTERN CAGradientLayerType const kCAGradientLayerAxial
    API_AVAILABLE(macos(10.6), ios(3.0), watchos(2.0), tvos(9.0));
//径向梯度。渐变被定义为一个椭圆,它的中心是` startPoint `,宽度和高度定义为` (endPoint.x - startPoint.x) * 2 ` 和 `(endPoint.y - startPoint.y) * 2'。
//径向梯度是沿两个限定端之间的轴径向变化的填充,其通常都是圆
CA_EXTERN CAGradientLayerType const kCAGradientLayerRadial
    API_AVAILABLE(macos(10.6), ios(3.2), watchos(2.0), tvos(9.0));
//锥形渐变。梯度以“startPoint”为中心,其0度方向由“startPoint”和“endPoint”之间的矢量定义。
//当“startPoint”和“endPoint”重叠时,结果未定义。梯度的角度在正x轴朝向正y轴的旋转方向上增加。
CA_EXTERN CAGradientLayerType const kCAGradientLayerConic
    API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0));

\color{red}{例如设置视图背景渐变色的代码示例:}

- (void)setGradualChangeColorView:(UIView *)view{
    
    //CAGradientLayer继承CALayer,可以设置渐变图层
    CAGradientLayer *grandientLayer = [[CAGradientLayer alloc] init];
    grandientLayer.frame = view.bounds;
    [view.layer addSublayer:grandientLayer];
    [view.layer insertSublayer:grandientLayer atIndex:0];
    //设置渐变的方向 左上(0,0)  右下(1,1)
    grandientLayer.startPoint = CGPointZero;
    grandientLayer.endPoint = CGPointMake(0.0, 1.0);
    //colors渐变的颜色数组 这个数组中只设置一个颜色是不显示的
    grandientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor greenColor].CGColor];
    grandientLayer.type = kCAGradientLayerAxial;
    
}
- (void)viewDidLoad {

    [super viewDidLoad];
    UIView *view = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMidX(self.view.frame) - 50, CGRectGetMidY(self.view.frame) - 50, 100, 100)];
    [self.view addSubview:view];
    [self setGradualChangeColorView:view];
  
}
屏幕快照 2019-10-30 下午10.50.18.png

CAReplicatorLayer -- 复制层

CAReplicatorLayer继承自CALayer,是一个Layer容器,添加到容器上的子Layer可以复制若干份,可以设定子Layer复制份数、设定副本之间的距离、透明度、颜色、旋转、位置等,可以使用CAReplicatorLayer对象来基于单一源层构建复杂的布局。

CAReplicatorLayer的属性
@property CFTimeInterval instanceDelay;

属性描述指定复制每个图层副本之间的延迟(以秒为单位),可设置动画,默认值为0.0,这意味着添加到被复制出的图层副本上的任何动画都将同步。

@property NSInteger instanceCount;

属性描述要通过复制创建的图层副本数量,其中包括源图层,默认值为1,不创建额外图层副本。

@property CATransform3D instanceTransform;

属性描述通过对前一实例的矩阵转换来生成当前实例,可以做成动画,转换的矩阵相对于该图层中心的位置应用,默认是单位矩阵。

@property BOOL preservesDepth;

属性描述定义此层是否将其子层展平到其平面中。如果YES,则该层的行为与CATTransfermLayer相似,并且具有相同的限制,默认值为NO。

@property(nullable) CGColorRef instanceColor;

属性描述定义源图层对象的颜色,可以做成动画,默认为不透明白色。

@property float instanceRedOffset;

属性描述定义添加到每个复制实例的颜色的红色分量的偏移量,可设置动画,通过对前一实例颜色的红色分量添加instanceRedOffset以生成当前实例的调制颜色。

@property float instanceGreenOffset;

属性描述定义添加到每个复制实例的颜色的绿色分量的偏移量,可设置动画,通过对前一实例颜色的绿色分量添加instanceGreenOffset以生成当前实例的调制颜色。

@property float instanceBlueOffset;

属性描述定义添加到每个复制实例的颜色的蓝色分量的偏移量,可设置动画,通过对前一实例颜色的蓝色分量添加instanceBlueOffset以生成当前实例的调制颜色。

@property float instanceAlphaOffset;

属性描述定义添加到每个复制实例的颜色的透明度分量的偏移量,可设置动画,通过对前一实例颜色的透明度分量添加instanceAlphaOffset以生成当前实例的调制颜色。

\color{red}{文档中提供的简单示例:}

创建一个网格,第一行五个正方形颜色从白色渐变为红色,垂直方向上每个正方形红色偏移量值逐渐减小 :

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化红色方块层对象
    CALayer *redSquare = [CALayer layer];
    redSquare.backgroundColor = [UIColor whiteColor].CGColor;
    redSquare.borderWidth = 1.0;
    redSquare.borderColor = [UIColor greenColor].CGColor;
    redSquare.frame = CGRectMake(0, 0, 25, 25);
    //初始化复制层对象
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    //通过复制创建的图层副本数
    NSInteger instanceCount = 5;
    replicatorLayer.instanceCount = instanceCount;
    //通过对前一实例的矩阵转换来生成当前实例
    replicatorLayer.instanceTransform = CATransform3DMakeTranslation(50, 0, 0);
    //设置颜色分量偏移量
    CGFloat offsetStep = -1.0 / instanceCount;
    replicatorLayer.instanceBlueOffset = offsetStep;
    replicatorLayer.instanceGreenOffset = offsetStep;
    //添加红色方块层对象到复制层
    [replicatorLayer addSublayer:redSquare];
    //初始化外层复制层对象
    CAReplicatorLayer *outerReplicatorLayer = [CAReplicatorLayer layer];
    //嵌套复制层
    [outerReplicatorLayer addSublayer:replicatorLayer];
    //通过复制创建的图层副本数
    outerReplicatorLayer.instanceCount = instanceCount;
    //通过对前一实例的矩阵转换来生成当前实例
    outerReplicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, 110, 0);
    //设置颜色分量偏移量
    outerReplicatorLayer.instanceRedOffset = offsetStep;
    //显示外层复制层对象
    outerReplicatorLayer.position = CGPointMake(0, 200);
    [self.view.layer addSublayer:outerReplicatorLayer];
}

截屏2022-07-27 15.41.48.png

\color{red}{旋转的圆圈:}

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化圆点图层
    CALayer *circle = [CALayer layer];
    circle.frame = CGRectMake(0, 0, 10, 10);
    circle.backgroundColor = UIColor.blueColor.CGColor;
    circle.cornerRadius = 5;
    circle.position = CGPointMake(0, 50);
    //圆点图层上的透明度动画
    CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeOut.fromValue = @(1);
    fadeOut.toValue = @(0);
    fadeOut.duration = 1;
    fadeOut.repeatCount = CGFLOAT_MAX;
    [circle addAnimation:fadeOut forKey:nil];
    //复制层
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    //添加圆点图层到复制层
    [replicatorLayer addSublayer:circle];
    //通过复制创建的图层副本数
    NSNumber *instanceCount = @(30);
    replicatorLayer.instanceCount = instanceCount.integerValue;
    //指定复制每个图层副本之间的延迟
    replicatorLayer.instanceDelay = fadeOut.duration / instanceCount.doubleValue;
    //过对前一实例的矩阵转换来生成当前实例
    CGFloat angle = -M_PI * 2 / instanceCount.floatValue;
    replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0, 0, 1);
    //显示复制层对象
    replicatorLayer.frame = CGRectMake(CGRectGetMidX(self.view.frame) - 50, CGRectGetMidY(self.view.frame) - 50, 100, 100);
    [self.view.layer addSublayer:replicatorLayer];
}
Jietu20220727-154855-HD.gif

\color{red}{简单的震动条示例:}

@interface TestCodeController ()

@end

@implementation TestCodeController

- (void)viewDidLoad {

    [super viewDidLoad];
    
    UIView *aniView = [[UIView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width / 2 - 40, CGRectGetMidY(self.view.frame) , 5, 20)];
    [self.view addSubview:aniView];
    [aniView.layer addSublayer: [self replicatorLayer_Shake]];

}

// 震动条动画
- (CALayer *)replicatorLayer_Shake{
    //初始化层对象
    CALayer *layer = [[CALayer alloc]init];
    //层对象框架矩形
    layer.frame = CGRectMake(0, 0, 10, 32);
    //设置圆角
    layer.cornerRadius = 5.0f;
    //设置层背景色
    layer.backgroundColor = [UIColor redColor].CGColor;
    //定义层边界矩形的定位点,可设置动画
    layer.anchorPoint = CGPointMake(0.5, 0.5);
    // 添加一个对Y方向进行缩放的基本动画
    [layer addAnimation:[self scaleYAnimation] forKey:@"scaleAnimation"];
    
    //初始化复制层
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    //复制层框架矩形
    replicatorLayer.frame = CGRectMake(0, 0, 80, 80);
    //要创建的副本数
    replicatorLayer.instanceCount = 3;
    //复制矩阵
    replicatorLayer.instanceTransform =  CATransform3DMakeTranslation(20, 0, 0);
    //指定复制副本之间的延迟(秒)
    replicatorLayer.instanceDelay = 0.2;
    //为每个复制的实例定义添加到组件的颜色的绿色偏移量,可设置动画
    replicatorLayer.instanceGreenOffset = 10;
    //将层对象添加到复制层容器
    [replicatorLayer addSublayer:layer];
    //返回复制层容器
    return replicatorLayer;
}

//对Y方向进行缩放的基本动画
- (CABasicAnimation *)scaleYAnimation{
    //对Y方向进行缩放的基本动画
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
    //缩放结束值
    anim.toValue = @0.1;
    //动画持续时间
    anim.duration = 0.4;
    //动画完成后是否在层中移除
    anim.removedOnCompletion = NO;
    //动画结束是否执行逆动画
    anim.autoreverses = YES;
    //动画重复次数为无限重复
    anim.repeatCount = MAXFLOAT;
    //返回/对Y方向进行缩放的基本动画
    return anim;
}

@end

效果如图:

Jietu20200924-162759.gif

CAAnimation - 动画的抽象超类

CAAnimation,核心动画中动画的抽象超类。继承自NSObject,遵循了NSSecureCoding、 NSCopying,、CAMediaTiming,、CAAction协议,CAAnimation为CAMediaTiming和CAAction协议提供了基本的支持。不要创建CAAnimation实例:要使核心动画层或SceneKit对象具有动画效果,请创建具体子类CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup或CATransition的实例。

CAAnimation提供的属性
@property(nullable, strong) CAMediaTimingFunction *timingFunction;

属性描述一个可选的计时函数,用来定义动画的节奏。默认为nil,表示线性节奏。

@property(nullable, strong) id <CAAnimationDelegate> delegate;

属性描述指定调用方的代理对象,代理对象由调用方保留。CAAnimation的实例不应该被设置为它自己的代理,这样做将导致保留周期。

@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

属性描述确定动画是否在完成时从目标图层的动画中移除。如果为“YES”,动画将在其活动持续时间过后从目标图层的动画中移除。默认为“YES”。

CAAnimation提供的函数
+ (instancetype)animation;

函数描述 : 创建并返回一个新的CAAnimation实例。

+ (nullable id)defaultValueForKey:(NSString *)key;

属性描述 : 使用指定键指定调用方属性的默认值。如果此方法返回nil,则根据键的声明类型为属性提供一个合适的“zero”默认值。例如,如果key是CGSize对象,则返回大小为(0.0,0.0)的值;对于CGRect,返回一个空矩形;对于CGAffineTransform和CATransform3D,返回适当的单位矩阵。

参数 :

key :调用方属性之一的名称。

返回值 :

指定属性的默认值,如果没有设置默认值,则返回nil。

- (BOOL)shouldArchiveValueForKey:(NSString *)key;

属性描述指定是否要归档给定键的属性值。由对象的实现encodeWithCoder:调用,对象必须实现键归档,默认实现返回YES。

参数 :

key : 调用方属性之一的名称。

返回值 :

如果要归档指定的属性,则为“YES”,否则为“NO”。

CAAnimation提供的代理函数
- (void)animationDidStart:(CAAnimation *)anim;

函数描述 : 通知代理动画已开始

参数 :

anim : 已启动的CAAnimation对象。

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

函数描述 :通知代理动画已结束。动画结束的原因可能是已达到动画活动持续时间,或者动画在其附着的图层中被移除,如果动画因为达到其活动持续时间后而结束,flag为YES。

参数 :

anim :结束的动画对象。

flag : 指示动画是否因为达到其活动持续时间后而结束的标志。

CAMediaTiming协议

CAMediaTiming协议建模了一个分层计时系统,CALayer与CAAnimation 都实现了该协议,协议中定义了在一段动画内用来控制时间的属性的集合

@property CFTimeInterval duration;

属性描述指定动画每执行一次的基本持续时间(秒),默认为值为0,但仍旧有0.25秒的动画。

@property float speed;

属性描述相对于duration所设置时间的流逝倍速,值越大则duration所设置的流逝时间越快。例如duration设置动画持续时间为10秒,speed设置为2.0,则5秒即完成动画。

@property CFTimeInterval timeOffset;

属性描述时间偏移,动画会在设置的偏移秒数开始执行,并执行duration所设置的持续时间。例如duration设置动画持续时间为10秒,timeOffset设置为5.0,则动画在5秒处开始,并运行10秒。

@property float repeatCount;

属性描述动画的重复次数,如果repeatCount为0,则忽略该属性,默认为0,将此属性设置为HUGE_VALF将导致动画永远重复。如果同时指定了repeatDuration和repeatCount,则行为未定义。

@property CFTimeInterval repeatDuration;

属性描述动画将重复执行的时间(秒),如果小于duration所设置时间,则动画只执行repeatDuration所设置时间(秒)的部分,如果大于duration所设置时间,动画将重复执行repeatDuration超出duration所设置时间的部分。该属性不受speed属性的影响,且与repeatDuration属性冲突。为0则忽略该属性,默认为0。

@property BOOL autoreverses;

属性描述动画在执行一次后是否执行逆动画,如果为YES,动画在正向执行一次后,反向执行一次。默认为NO。

@property(copy) CAMediaTimingFillMode fillMode;

属性描述指定动画开始之前与结束之后对动画时间上的一种填充,默认值为kCAFillModeRemoved,可选值如下:

//当动画完成时,以最终的可见状态作为填充
CA_EXTERN CAMediaTimingFillMode const kCAFillModeForwards
    API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
//动画开始之前,以动画的第一帧作为填充
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBackwards
    API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
//结合kCAFillModeForwards与kCAFillModeBackwards,即以最终的可见状态填充动画完成时,以动画的第一帧填充动画开始之前
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBoth
    API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
//动画完成后将移除,无填充
CA_EXTERN CAMediaTimingFillMode const kCAFillModeRemoved
    API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@property CFTimeInterval beginTime;

属性描述如果适用,指定相对于其父对象的开始时间(延迟时间)。例如在动画在CAAnimationGroup动画组中,那么动画的开始时间是相对于CAAnimationGroup动画组动画开始的延迟,如果直接添加在CALayer(图层)中,则需要获取CALayer(图层)被添加的时间后,根据CALayer(图层)被添加的时间设置延迟。

CATransition - 转场动画

CATransition继承自CAAnimation,在图层状态之间提供动画转换的对象。通过创建并向层中添加CATransition对象,可以在图层的状态之间进行转换,默认过渡是交叉淡入,但可以指定与一组预定义过渡不同的效果。

CATransition提供的属性
@property(copy) CATransitionType type;

属性描述指定预定义的转换类型。可能的值以常见的转换类型显示。如果在筛选器属性中指定了自定义转换,则忽略此属性。默认值为kCATransitionFade。

//当图层变得可见或隐藏时,其内容将逐渐消失。
CA_EXTERN CATransitionType const kCATransitionFade
//该层的内容在任何现有内容上滑动到位。公共转换子类型用于此转换。
CA_EXTERN CATransitionType const kCATransitionMoveIn
//该层的内容推动任何现有内容,因为它滑动到位。公共转换子类型用于此转换。
CA_EXTERN CATransitionType const kCATransitionPush
//层的内容按转换子类型指定的方向逐渐显示。公共转换子类型用于此转换。
CA_EXTERN CATransitionType const kCATransitionReveal
@property(nullable, copy) CATransitionSubtype subtype;

属性描述指定一个可选子类型,该子类型预定义了基于运动的变换方向。可能的值显示在公共转换子类型中,默认值为nil。

//过渡从层的右侧开始。
CA_EXTERN CATransitionSubtype const kCATransitionFromRight
//过渡从层的左侧开始。
CA_EXTERN CATransitionSubtype const kCATransitionFromLeft
//过渡从层的顶部开始。
CA_EXTERN CATransitionSubtype const kCATransitionFromTop
//过渡从层的底部开始。
CA_EXTERN CATransitionSubtype const kCATransitionFromBottom
@property float startProgress;

属性描述指示调用方的起点,作为整个转换的一部分,合法值是介于0.0和1.0之间的数字。例如,要在转换过程的一半开始,请将startProgress设置为0.5。默认值为0。

@property float endProgress;

属性描述指示调用方的终点,作为整个转换的一部分,该值必须大于或等于startProgress,且不大于1.0。如果endProgress小于startProgress,则行为未定义。默认值是1.0。

\color{red}{CATransition简单的代码示例:}

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    UIButton *transitionBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [transitionBtn setTitle:@"转场动画" forState:UIControlStateNormal];
    [transitionBtn setBackgroundColor:[UIColor blueColor]];
    transitionBtn.titleLabel.font = [UIFont systemFontOfSize:15];
    [transitionBtn addTarget:self action:@selector(transitionBtnClick) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:transitionBtn];
    [transitionBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view).offset(100);
        make.centerX.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(75, 35));
    }];
    
}

//转场动画按钮点击
- (void)transitionBtnClick{
    
    UIView *view = [[UIView alloc]initWithFrame:CGRectZero];
    view.backgroundColor = [UIColor redColor];
    
    CATransition *animation = [CATransition animation];
    animation.delegate = self;
    //动画持续时间
    animation.duration = 0.4;
    //减缓速度,这会导致动画快速开始,然后随着它的进展而缓慢。
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    //动画为推出类型
    animation.type = kCATransitionPush;
    //方向,向上方推出
    animation.subtype = kCATransitionFromTop;
    //添加动画
    [view.layer addAnimation:animation forKey:@"animation1"];
    //布局视图
    [self.view addSubview:view];
    [view mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(self.view.mas_bottom);
        make.size.mas_equalTo(CGSizeMake(SCREEN_WIDTH, 360));
    }];
    
}

简单的推出效果 :


Jietu20200104-152550.gif

CAAnimationGroup - 分组动画

CAAnimationGroup继承自CAAnimation,允许多个动画被分组并同时运行的对象。分组中添加的动画会在CAAnimationGroup实例指定的时间空间中运行,分组中添加的动画时间如果超过CAAnimationGroup实例设置的动画持续时间,则添加的动画被剪切到动画组的持续时间。例如,在动画组中添加的动画持续时间10秒,但CAAnimationGroup实例设置的持续时间为5秒,则只显示动画的前5秒。

动画数组中添加的动画其设置的代理和removedOnCompletion属性目前会被忽略。由CAAnimationGroup的代理接收这些消息。

CAAnimationGroup提供的属性

@property(nullable, copy) NSArray<CAAnimation *> *animations;

属性描述 : 要在调用方的时间空间内计算的一组CAAnimation对象。动画同时在接收方的时间空间中运行。

@property(nullable, copy) NSArray<CAAnimation *> *animations;

\color{red}{CAAnimationGroup简单的代码示例:}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = [UIColor blueColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [button.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
    
    self.myView = [[MyView alloc]initWithFrame:CGRectZero];
    self.myView.layer.contents = (id)[UIImage imageNamed:@"darts"].CGImage;
    [self.view addSubview:self.myView];
    self.myView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.myView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [self.myView.widthAnchor constraintEqualToConstant:100],
        [self.myView.heightAnchor constraintEqualToConstant:100],
    ]];
}

- (void)buttonClick:(UIButton *)sender {
    
    CABasicAnimation * rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotateAnimation.fromValue = [NSNumber numberWithFloat:0];
    rotateAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2 + M_LN2];
    rotateAnimation.duration = 0.25f;
    rotateAnimation.repeatCount = HUGE_VALF;
    rotateAnimation.beginTime = 1.0f;
    
    CAKeyframeAnimation* frameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
    frameAnimation.values = @[@50,@(UIScreen.mainScreen.bounds.size.width - 50)];
    frameAnimation.keyTimes = @[@0,@1];

    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = @[rotateAnimation,frameAnimation];
    group.duration = 5.0f;
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    [self.myView.layer addAnimation:group forKey:nil];
}

旋转加移动 :

Jietu20220713-171323-HD.gif

CAPropertyAnimation - 属性动画

继承自CAAnimation,是CAAnimation的一个抽象子类,用于创建操作图层属性值的动画,通常不直接使用,而是使用其子类CABasicAnimation或CAKeyframeAnimation。

CAPropertyAnimation提供的属性
@property(nullable, copy) NSString *keyPath;

属性描述对某个属性需要设置动画的字符串描述,例如使用这一字符串@"position.x"描述动画在CGPoint中心点X轴方向改变位置。

@property(getter=isAdditive) BOOL additive;

属性描述 : 在动画每次执行后,是否将动画指定的值添加到当前渲染树中的值以生成新的渲染树值,如果为YES,动画指定的值将会添加到属性的当前渲染树值中,以生成新的渲染树值。例如在keypath为@"position.x"描述的动画中,该属性为YES时,position.x += value,该属性为NO时,position.x = value。该属性默认为NO。(注:在动画设置重复执行时,在重复的过程中,该值为YES,值也不会在动画重复过程中累加)

例如在下面的代码中,可以比较直观的观察additive属性变化的不同效果:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = [UIColor blueColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [button.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
    
    self.myView = [[MyView alloc]initWithFrame:CGRectZero];
    self.myView.layer.contents = (id)[UIImage imageNamed:@"darts"].CGImage;
    [self.view addSubview:self.myView];
    self.myView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.myView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [self.myView.widthAnchor constraintEqualToConstant:100],
        [self.myView.heightAnchor constraintEqualToConstant:100],
    ]];
}

- (void)buttonClick:(UIButton *)sender {
    CABasicAnimation * rotateAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];
    rotateAnimation.fromValue = @(50);
    rotateAnimation.toValue = @(100);
    rotateAnimation.duration = 1.0f;
    rotateAnimation.repeatCount = 1;
    rotateAnimation.fillMode = kCAFillModeForwards;
    rotateAnimation.removedOnCompletion = NO;
    rotateAnimation.additive = YES;
    [self.myView.layer addAnimation:rotateAnimation forKey:nil];
}

当additive为YES时,点击执行三次动画:

Jietu20220714-140139-HD.gif

当additive为NO时,点击执行三次动画:

Jietu20220714-140253-HD.gif
@property(getter=isCumulative) BOOL cumulative;

属性描述 : cumulative属性影响重复动画生成结果的方式。如果为YES,则动画的当前值是上一个重复周期结束时的值,加上当前重复周期的值。如果为NO,则该值只是为当前重复周期计算的值。默认为NO。与additive效果类似,不过该属性是作用在动画重复过程中的。

@property(nullable, strong) CAValueFunction *valueFunction;

属性描述 : 如果为非nil值,则定义了为目标属性插入新值的动画方式的函数,默认为nil。valueFunction是专门为了transform动画而设置的,包括旋转、缩放、平移等。

\color{red}{例如在X轴线方向平移 :}

- (void)buttonClick:(UIButton *)sender {
    
    CABasicAnimation * rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    rotateAnimation.fromValue = [NSNumber numberWithFloat:0];
    rotateAnimation.toValue = [NSNumber numberWithFloat:100];
    rotateAnimation.duration = 1.0f;
    rotateAnimation.repeatCount = HUGE_VALF;
    rotateAnimation.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionTranslateX];
    [self.myView.layer addAnimation:rotateAnimation forKey:nil];
}
CAPropertyAnimation提供的函数
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;

函数描述使用指定的path描述的属性创建CAPropertyAnimation实例,例如使用这一字符串@"position.x"创建CGPoint中心点X轴方向改变位置的CAPropertyAnimation实例。

参数 :

path : 描述属性的字符串。

返回值 : CAPropertyAnimation实例,并且该实例的keyPath设置为path。

CAKeyframeAnimation - 关键帧动画

CAKeyframeAnimation继承自CAPropertyAnimation,为CALayer(图层)对象提供关键帧动画功能的对象(在某一时间段动画进行到某一帧)。使用继承的animationWithKeyPath:方法创建CAKeyframeAnimation对象,keyPath指定要在图层上进行动画的图层框架属性,然后可以通过指定values和动画计时(例如keyTimes)控制动画行为。

在动画过程中,Core animation通过插入提供的值来生成中间值,例如当为一个坐标点(比如图层的位置)设置动画时,可以为这个点指定一条由values构成的路径,而不是单独的值。动画的速度由提供的时间信息控制。

CAKeyframeAnimation提供的属性
@property(nullable, copy) NSArray *values;

属性描述为CAKeyframeAnimation实例提供动画功能值的对象数组。values表示CAKeyframeAnimation实例动画必须通过的值。将给定的values中的值应用到添加动画的图层的时间取决于动画计时,动画计时由calculationMode、keyTimes和timingFunctions属性控制。应用于CAKeyframeAnimation实例之间的值是使用插值创建的,除非计算模式设置为kCAAnimationDiscrete。

根据属性的类型,可能需要用NSNumber的NSValue对象包装这个数组中的值。对于某些核心图形数据类型,可能还需要在将它们添加到数组之前将它们转换为id。

此属性中的值仅在path属性中的值为nil时使用。

@property(nullable) CGPathRef path;

属性描述分配给此属性的路径对象定义了图层属性(图层属性包含 CGPoint 数据类型)在动画长度上的值。如果为此属性指定值,则会忽略 values 属性中的任何数据。

为动画指定的任何计时值(keyTimes)都将应用于用于创建路径的点,路径可以包含定义移动到线段的点(moveToPoint:),以直线添加到线段的点或以曲线添加到线段的点,直线段或曲线段会在线段的终点定义关键帧值,然后在线段起点(前一条线段的终点)与线段终点之间的所有其他的点进行插值,以移动的方式添加到线段的点不定义单独的关键帧值。

动画沿路径运行的方式取决于calculationMode 属性中的值。要实现沿路径的平滑、匀速动画,请将calculationMode 属性设置为kCAAnimationPaced 或kCAAnimationCubicPaced;要创建位置值从关键帧点跳转到关键帧点(中间没有插值)的动画,请使用 kCAAnimationDiscrete 值;要通过在点之间插值沿路径设置动画,请使用kCAAnimationLinear值。

@property(copy) CAAnimationCalculationMode calculationMode;

属性描述 :calculationMode可能的值有“discrete”、“linear”、“paced”、“cubic”和“cubicPaced”。默认为“ linear”。当设置为paced或cubicPaced时,动画的keyTimes和timingFunctions属性将被忽略并隐式计算。

//关键帧值之间的简单线性计算。
CA_EXTERN CAAnimationCalculationMode const kCAAnimationLinear
//依次使用每个关键帧值,不计算插值。
CA_EXTERN CAAnimationCalculationMode const kCAAnimationDiscrete
//对线性关键帧值进行插值以在整个动画中产生均匀的速度。
CA_EXTERN CAAnimationCalculationMode const kCAAnimationPaced
//关键帧值之间的平滑样条线计算。。
CA_EXTERN CAAnimationCalculationMode const kCAAnimationCubic
//对三次关键帧值进行插值以在整个动画中产生均匀的速度。
CA_EXTERN CAAnimationCalculationMode const kCAAnimationCubicPaced
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;

属性描述应用于CAKeyframeAnimation实例动画的可选数组(数组中存放NSNumber对象),定义了动画的时间段,数组中的每个值是0.0(起始时间)到1.0(结束时间)之间的浮点数,数组中的每个后续值必须大于或等于前一个值,这些值代表在动画总持续时间中的一个时间点。通常,数组中的元素数量应与values属性中的元素数量或path属性中的控制点数量匹配,如果它们之间是不匹配的,则动画的效果可能不是所期望的。

如何在数组中包含适当的值取决于calculationMode属性:

  • 如果calculationMode设置为kCAAnimationLinear或kCAAnimationCubic,则数组中的第一个值必须是0.0,最后一个值必须是1.0,所有中间值表示开始和结束时间之间的时间点。

  • 如果calculationMode设置为kCAAnimationDiscrete,则数组中的第一个值必须是0.0,最后一个值必须是1.0,数组中应该比values数组中多一个条目。例如,如果values数组中有两个值,那么该有三个时间点的值。

  • 如果calculationMode设置为kCAAnimationPaced或kCAAnimationCubicPaced,则忽略此属性中的值。

如果此数组中的值无效或不适合当前计算模式,则忽略它们。

@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;

属性描述应用于CAKeyframeAnimation实例动画的可选数组(数组中存放CAMediaTimingFunction对象),可以使用此数组定义values数组中两个值节点之间的动画节奏,可以使动画线性过渡、或缓慢开始后加速过渡、或加速开始后缓慢过渡。如果values属性中的元素为n,则此数组中元素应包含n-1个对象。

此属性通过动画计时控制动画节奏,如果在keyTimes属性中提供了计时信息,则此属性指定的动画节奏计时将在keyTimes属性提供的值之间进行进一步修改。如果未为keyTimes属性指定值,则此属性指定的动画节奏计时将修改动画对象提供的默认计时。

如果还在动画对象的timingFunction属性中指定动画节奏计时函数,则会首先应用timingFunction属性中指定的动画节奏计时函数,然后应用特定关键帧时间段的计时函数。

@property(nullable, copy) CAAnimationRotationMode rotationMode;

属性描述定义沿路径设置动画的对象是否旋转以匹配路径切线,默认为nil,可以设置的值为“auto”和“autoReverse”。在为提供路径对象时为此属性设置值则行为是为定义的 。。

CAAnimationRotationMode提供的值 :

//物体沿着与路径相切的方向运动。
CA_EXTERN CAAnimationRotationMode const kCAAnimationRotateAuto
//物体以与路径相切的180度移动。
CA_EXTERN CAAnimationRotationMode const kCAAnimationRotateAutoReverse

\color{red}{CAKeyframeAnimation简单的代码示例:}

通过values设置关键点 :

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = [UIColor blueColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [button.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
    
    self.myView = [[MyView alloc]initWithFrame:CGRectZero];
    self.myView.layer.contents = (id)[UIImage imageNamed:@"darts"].CGImage;
    [self.view addSubview:self.myView];
    self.myView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.myView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [self.myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [self.myView.widthAnchor constraintEqualToConstant:100],
        [self.myView.heightAnchor constraintEqualToConstant:100],
    ]];
}

- (void)buttonClick:(UIButton *)sender {
    //获取视图的中心点X轴坐标点
    CGFloat myViewCenterX = self.myView.center.x;
    //初始化在中心点在X轴坐标移动的关键帧动画
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
    //动画持续时间
    animation.duration = 5.0f;
    //动画的5个关键点
    animation.values = @[@(myViewCenterX),@(myViewCenterX - 150),@(myViewCenterX),@(myViewCenterX + 150),@(myViewCenterX)];
    //动画到达5个关键点的时间节点
    animation.keyTimes = @[@0,@0.25,@0.50,@0.75,@1];
    //在每两个时间节点之间的动画节奏
    animation.timingFunctions = @[
        [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
        [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
        [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
        [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    //添加动画
    [self.myView.layer addAnimation:animation forKey:nil];
}

代码效果 :

Jietu20220715-172745-HD.gif

通过path设置路径 :

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = [UIColor blueColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [button.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
    
    self.myView = [[MyView alloc]initWithFrame:CGRectZero];
    self.myView.layer.contents = (id)[UIImage imageNamed:@"darts"].CGImage;
    [self.view addSubview:self.myView];
    self.myView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.myView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [self.myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [self.myView.widthAnchor constraintEqualToConstant:100],
        [self.myView.heightAnchor constraintEqualToConstant:100],
    ]];
}

- (void)buttonClick:(UIButton *)sender {
    //曲线
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(CGRectGetMidX(self.view.frame) - 200 / 2, CGRectGetMidY(self.view.frame) - 150 / 2, 200, 150)];
    //形状层
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    //路径形状填充颜色
    shapeLayer.fillColor = nil;
    //路径的线宽
    shapeLayer.lineWidth = 2.5f;
    //路径颜色
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    //渲染的形状的路径
    shapeLayer.path = path.CGPath;
    //添加形状层
    [self.view.layer addSublayer:shapeLayer];

    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = path.CGPath;
    //动画持续时间
    animation.duration = 5.0f;
    //动画运行方式
    animation.calculationMode = kCAAnimationCubicPaced;
    //动画重复次数
    animation.repeatCount = HUGE_VALF;
    //自动旋转匹配切线
    animation.rotationMode = kCAAnimationRotateAuto;
    //添加动画
    [self.myView.layer addAnimation:animation forKey:nil];
}

代码效果 :

Jietu20220722-143502-HD.gif

CABasicAnimation -- 基本的单一关键帧动画

继承自CAPropertyAnimation,为图层(CALayer)的属性提供基本的单一关键帧动画功能的对象。使用继承的animationWithKeyPath:方法创建CABasicAnimation的实例,并在关键路径(KeyPath)中指定要在渲染树中添加动画的属性的。例如:[CABasicAnimation animationWithKeyPath:@"transform.rotation"]描述了了一个旋转动画。

以下是定义在其间插值的属性值的对象,所有选项都是可选的,而且非nil的属性不应该超过两个,对象类型应该与被动画的属性类型相匹配(使用CALayer.h中描述的标准规则)。支持的动画模式有:

“fromValue”和“toValue”都不为nil,在fromValue和toValue之间进行插值。

“fromValue”和“byValue”不为nil,在“fromValue”和“fromValue”加上“byValue”之间进行插值。

“byValue”和“toValue”不为nil,在“toValue”减去“byValue”和“toValue”之间进行插值。

“fromValue”非nil,在“fromValue”和属性的当前表示值之间进行插值。

“toValue”非nil,在渲染树中图层的当前属性值和“toValue”之间进行插值。

“byValue”非nil,在渲染树中图层的当前属性值和加上“byValue”的值之间进行插值。

CABasicAnimation的属性
@property(nullable, strong) id fromValue;

属性描述 : 定义调用方用于开始插值的值

@property(nullable, strong) id toValue;

属性描述 : 定义调用方用于结束插值的值

@property(nullable, strong) id byValue;

属性描述 : 定义调用方用于执行相对插值的值

\color{red}{CABasicAnimation简单的代码示例:}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 设置视图
    UIView *view = [[UIView alloc]initWithFrame:CGRectZero];
    view.backgroundColor = [UIColor redColor];
    [self.view addSubview:view];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [view.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [view.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [view.widthAnchor constraintEqualToConstant:100],
        [view.heightAnchor constraintEqualToConstant:100],
    ]];
    // 设置动画为旋转
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // 动画持续时间
    animation.duration = 5.0f;
    // 重复次数(HUGE_VALF为无限重复)
    animation.repeatCount = HUGE_VALF;
    // 开始时的位置
    animation.fromValue = [NSNumber numberWithFloat:0];
    // 结束时的位置
    animation.toValue = [NSNumber numberWithFloat:M_PI * 2];
    // 添加动画
    [view .layer addAnimation:animation forKey:nil];
}

简单的旋转:

Jietu20220722-163511-HD.gif

对keypath提供的简单动画的效果的汇总

//CATransform3D Key Paths
static NSString *kCARotation = @"transform.rotation";            //旋转
static NSString *kCARotationX = @"transform.rotation.x";         //围绕X轴旋转
static NSString *kCARotationY = @"transform.rotation.y";         //围绕Y轴旋转
static NSString *kCARotationZ = @"transform.rotation.z";         //围绕Z轴旋转
//缩放
static NSString *kCAScale = @"transform.scale";                  //缩放
static NSString *kCAScaleX = @"transform.scale.x";               //对X方向进行缩放
static NSString *kCAScaleY = @"transform.scale.y";               //对Y方向进行缩放
static NSString *kCAScaleZ = @"transform.scale.z";               //对Z方向进行缩放
//平移
static NSString *kCATranslation = @"transform.translation";      //平移
static NSString *kCATranslationX = @"transform.translation.x";   //对X方向进行平移
static NSString *kCATranslationY = @"transform.translation.y";   //对Y方向进行平移
static NSString *kCATranslationZ = @"transform.translation.z";   //对Z方向进行平移
//平面
static NSString *kCAPosition = @"position";                      //CGPoint中心点改变位置
static NSString *kCAPositionX = @"position.x";                   //CGPoint中心点X方向改变位置
static NSString *kCAPositionY = @"position.y";                   //CGPoint中心点Y方向改变位置
//CGRect
static NSString *kCABoundsSize = @"bounds.size";                 //自身界限的大小
static NSString *kCABoundsSizeW = @"bounds.size.width";          //自身界限的宽度
static NSString *kCABoundsSizeH = @"bounds.size.height";         //自身界限的高度
static NSString *kCABoundsOriginX = @"bounds.origin.x";          //自身界限的起点X方向
static NSString *kCABoundsOriginY = @"bounds.origin.y";          //自身界限的起点Y方向
//Layer
static NSString *kCAOpacity = @"opacity";                        //透明度
static NSString *kCABackgroundColor = @"backgroundColor";        //背景色
static NSString *kCACornerRadius = @"cornerRadius";              //圆角
static NSString *kCABorderWidth = @"borderWidth";                //边框
static NSString *kCAShadowColor = @"shadowColor";                //阴影颜色
static NSString *kCAShadowOffset = @"shadowOffset";              //偏移量CGSize
static NSString *kCAShadowOpacity = @"shadowOpacity";            //阴影透明度
static NSString *kCAShadowRadius = @"shadowRadius";              //阴影圆角

CASpringAnimation - 弹簧力动画

CASpringAnimation继承自CABasicAnimation,对层的属性应用类似弹簧力的动画。通常使用弹簧动画设置层位置的动画,使其看起来被弹簧拉向目标。层离目标越远,对它的加速度就越大。CASpringAnimation允许控制基于物理的属性,例如弹簧的阻尼和刚度。

CASpringAnimation提供的属性
@property CGFloat mass;

属性描述 : 附着在弹簧末端的物体的质量,默认质量为1。增加该值将增加弹簧弹力效果,附加与弹簧动画的对象将受到更多的振荡次数和更大的弹力回调,从而增加动画沉降过程持续时间;减小该值将减少弹簧力效果,振荡次数与弹力回调减少,从而减少动画沉降过程持续时间。

@property CGFloat stiffness;

属性描述弹簧动画弹簧的刚度系数,默认的刚度系数为100。增加刚度可增加振荡次数,并增加动画沉降过程持续时间;减小刚度会减少振荡次数,并减少动画沉降过程持续时间。

@property CGFloat damping;

属性描述抑制弹簧弹力的阻尼值,默认值是10。减少这个值可以减少每次振荡的能量损失,动画值将超过toValue,并且使动画沉降过程持续时间大于动画持续时间。增加数值会增加能量损耗,振荡会越来越少,越来越小,动画沉降过程持续时间可能会小于动画持续时间。

@property CGFloat initialVelocity;

属性描述附着在弹簧上的物体的初始速度,默认为0,表示不移动的对象。负值表示附着在弹簧上对象先反向远离弹簧连接点,正值表示附着在弹簧上对象加速在弹簧附着点正向移动。

@property(readonly) CFTimeInterval settlingDuration;

属性描述弹簧系统返回到静止时要考虑的所需的估计持续时间,将为当前动画参数计算持续时间。

\color{red}{一个简单的掉落效果代码示例:}

@interface TSChannelFiveViewController ()<CAAnimationDelegate>

@property (nonatomic, strong) UIView *myView;
@property (nonatomic, strong) NSLayoutConstraint *myViewYConstraint;

@end

@implementation TSChannelFiveViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置按钮
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = [UIColor blueColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [button.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
    // 设置视图
    self.myView = [[UIView alloc]initWithFrame:CGRectZero];
    self.myView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.myView];
    UITapGestureRecognizer *singleClick = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleClickEvent)];
    [self.myView addGestureRecognizer:singleClick];
    self.myView.translatesAutoresizingMaskIntoConstraints = NO;
    self.myViewYConstraint = [self.myView.bottomAnchor constraintEqualToAnchor:self.view.topAnchor];
    [NSLayoutConstraint activateConstraints:@[
        [self.myView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        self.myViewYConstraint,
        [self.myView.widthAnchor constraintEqualToConstant:100],
        [self.myView.heightAnchor constraintEqualToConstant:100],
    ]];
    
}

/// 按钮点击事件
- (void)buttonClick:(UIButton *)button {
    // 设置动画为旋转
    CASpringAnimation * animation = [CASpringAnimation animationWithKeyPath:@"position.y"];
    animation.delegate = self;
    // 动画持续时间
    animation.duration = 2.0f;
    // 结束时的位置
    animation.toValue = @(CGRectGetMidY(self.view.frame));
    // 附着在弹簧上的物体质量
    animation.mass = 3;
    // 弹簧刚度
    animation.stiffness = 300;
    // 弹簧弹力的阻尼
    animation.damping = 9;
    // 使视图停留在动画结束的位置
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    // 添加动画
    [self.myView.layer addAnimation:animation forKey:nil];
}

/// 视图点击事件
- (void)singleClickEvent {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"点击了" message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        [alert dismissViewControllerAnimated:YES completion:nil];
    }];
    [alert addAction:cancelAction];
    [self presentViewController:alert animated:YES completion:nil];
}

#pragma mark - CAAnimationDelegate

/// 动画结束
/// @param anim 结束的动画对象
/// @param flag 指示动画是否因为达到其活动持续时间后而结束的标志
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (flag) {
        self.myViewYConstraint.active = NO;
        self.myViewYConstraint = [self.myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor];
        self.myViewYConstraint.active = YES;
    }
}

@end

在动画结束后更新了一下约束,因为层在动画完成后,虽然视图保留了动画完成时的位置,但视图的frame实际是没有改变的,如果没有更新约束,视图是不响应点击事件的,因为根本没有点击到视图上。效果如图:

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

推荐阅读更多精彩内容