核心动画(CoreAnimation)

1,570 阅读27分钟

前言

核心动画 CoreAnimation 是开发过程非常常用,其属于 QuartzCore框架,一些图形的绘制和动画的制作都在这个框架里,因此 CoreAnimation 的学习,动画只是冰山一角

---- 案例demo

核心动画介绍

核心动画 CoreAnimation 属于 QuartzCore框架,这里学习的内容如要包括了 CALayerCAAnimation相关

其中CALayer主要负责内容的渲染操作,因此动画的制作离不开CALayer,因此需要先介绍一下 CALayer

CALayer

渲染树 Layer Tree

提到 CALayer 不得不提到 Layer Tree, 理解了它,对于动画就会有了更深入的理解,如下所示为他们的介绍

Model Tree :也就是我们通常所说的layer,为默认图层

Presentation Tree:呈现出来的layer,也就是我们做动画时你看到的那个layer,可以通过layer.presentationLayer获得

Render Tree :私有,无法访问。主要是对Presentation Tree数据进行渲染,并且不会阻塞线程

在动画运行时,我们看到的是 Presentation Tree所展示出来的内容,动画执行完毕后,Tree 内容会同步一致 当恢复动画时,依靠 Model Tree可恢复 Presentation Tree

CALayer 与 UIView

看到 CALayer 是负责渲染的,可能回不理解,平时不就用了个 UIView,怎么扯出来个 CALayer,UIView也可以做动画呀

这里不得不解释下, UIView 的动画效果都是依赖 CoreAnimation 封装的部分功能,后面学习 Animation的时候会发现很多相似之处

其实UIViewCALayer相辅相成,共同构建出的一个能显示能交互的效果,其中 CALayer 负责渲染功能,UIView负责用户交互

可是为什么要这么设计呢?

个人猜测,苹果也是最后复用性以及代码量的,苹果有 MACOSIOS,两个系统的用户交互都不一样,难道做两份相同的渲染功能呢,这显然是不可以忍受的(至少个人是不能忍受的),从后面的 layer 的使用中,也可以看到一些痕迹

CALayer 与 UIView 的点击事件

既然 UIView 负责用户交互,CALayer 负责渲染,也就是说 CALayer 完全不能点击了,虽然是这么说,系统为了应付多变的开发需求,不仅给 UIView 封装渲染和动画相关API,那么 CALayer 也提供了一个 hitTest方法,可以通过点击位置,用来获取点击的 CALayer 图层,自己可以尝试一下哈

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    CGPoint point = [[touches anyObject] locationInView:self.view];
    CALayer *layer = [self.layerView.layer hitTest:point];
    //判断点击了相应的layer
    if (layer == self.blueLayer) {
    } else if (layer == self.layerView.layer) {
    }
}
UIView 与 事件响应链

UIView点击事件可能会用的两个方法

用户点击屏幕到响应的过程,系统一共分为两个大步骤

1、传递链: 用户点击事件点到 Window 的时候,在 UIWindow 后,向子视图遍历查找到能够响应用户条件的 View

2、响应链: 从符合条件的最上层开始,查看是否符合响应效果(例如: hiddenuserInteractionEnabled等),如果不符合条件,依次向下,直到找到 window 为止,符合条件则响应,找不到,则不响应该用户事件

下面是 UIView 点击事件中可能会用到的两个方法,如果自己自定义的 UIView,部分效果超出了父视图(例如:tableBar下方可能会有个大按钮突出),则可以通过重写下面方法,让其超出父视图部分也能点击

//检查点击事件是否在试视图中,如果自定义的视图子控件出界响应不到,可以可以重写
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//检查window中能点击条件的View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

CALayer基本属性

CALayer的部分属性设置默认支持隐式动画,如下图所示,是一些基本的属性介绍,隐式动画时间大约为 0.25s

8e35e087-f939-4acb-a0a9-aa503fc091a9.png

如果变换过程中不需要隐式动画,则可以通过 CATransaction 来取消调动画

//去除隐式动画
[CATransaction begin];
//[CATransaction setAnimationDuration: 1]; //可以通过该方法设置隐式动画时长
[CATransaction setDisableActions:YES]; //设置事件不可用,包括隐式动画
layer.opacity = 0;
[CATransaction commit];

下面详细介绍一下里面的一些特殊属性

contents

contents 属性是给 layer 赋予内容的,可以是图片,可以是文字,其为 id 类型,也就意味着,你可以赋值任意类型的内容

然而,实际却并非如此,如果在 ios 中赋值图片不是 CGImage,虽然编译动过,但是却无法展示, 原因主要是在 macos 中给 CGImageNSImage,所以会对移动端的赋值造成困扰

因此,给 contents 赋图片,需要赋值 CGImage 类型

//因为 CGImage 不是 NSObject 类型,因此需要桥接
layer.contents = (__bridge id)image.CGImage;

contentGravity

此属性和 UIView 中的 contentModel 一样,是设置 content 内容的显示位置的,主要有下面这些属性,相信一看便知

  • kCAGravityCenter
  • kCAGravityTop
  • kCAGravityBottom
  • kCAGravityLeft
  • kCAGravityRight
  • kCAGravityTopLeft
  • kCAGravityTopRight
  • kCAGravityBottomLeft
  • kCAGravityBottomRight
  • kCAGravityResize
  • kCAGravityResizeAspect
  • kCAGravityResizeAspectFill

contentsScale

contentsScale 属性和屏幕的 scale 相关,即像素尺寸和视图大小的比例,默认为1,例如现在的 iphone 手机,已经基本上都是 3x,即三倍 scale,这样如果 contentsScale,如果设置为 1 的话,那么内容可能显示的不是那么清晰,不过有 kCAGravityResizeAspect等属性加持,图片默认会缩放,一般不会有显示问题,如果有可以调整 contentsScale 的值

contentsRect

contentsRect 可以设置显示的区域内容,如果一张图片比较大,有好几片,内容,可以通过使用其,设置显示区域,来控制显示的内容,contentsRect默认区间为 0~1的矩形区间,如果大于1或者负数也是可以的(这样外面像素会被拉伸填充,不推荐这么玩)

8ebd94ba-559f-4d53-899c-2e67c95b24f2.png

maskToBounds

如果想且圆角,只需要设置 cornerRadius 即可,如果需要把子视图超过自己的部分隐藏掉,那么就需要 maskToBounds了,与 UIViewclipsToBounds 一样,功能都是切割越界的子视图

mask

图层蒙版,这个属性本身就是个CALayer类型,有和其他图层一样的绘制和布局属性,它类似于一个子图层,不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域,确定了layer的轮廓,因此mask图层的Color属性是无关紧要的

如果mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。

CAShapeLayer

CAShapeLayer也算是平时最常用的一个 layer 了,主要用来绘制特殊图形的,例如:矩形、三角形、圆形等,其一般与 UIBezierPath配合使用,画出更加符合效果的视图

fileColor: 表示layer的path的填充颜色,fillRule属性用于指定使用哪一种算法去判断画布上的某区域是否属于“该图形”内部

fileRule: 一共有两个美枚举类型,分别是kCAFillRuleNonZerokCAFillRUleEvenOdd,在填充复杂的闭合图形时,才是此属性的使用时机

kCAFillRuleNonZero是以穿过的射线来判断,从区域一个点,往外任意方向引出一条射线,如果被顺时针画出的线穿过则+1,被逆时针穿过的线-1结果等于0表示在外面,大于零表示在里面

kCAFillRUleEvenOdd,和上面的规则差不多,只是以奇偶数来判断内外,如果得出的值是技术则认为在内部,否则在外部

path: 贝塞尔曲线的路径

lineWidth: 边缘划线的宽度,这个设置了之后,贝塞尔曲线的就不管用了

stokeColor、fillColor: 分别是划线和填充的意思,即线条颜色和闭合区间内容填充颜色

strokeStart、strokeEnd: 表示的是设置绘制图像百分比的初始值、最终值,可以通过设置其,来控制线条从有到无的显示

lineJoin: 表示的是连接点的样式,分别是kCALineJoinMiter、kCALineJoinRound和kCALineJoinBevel,分别是尖角,圆角,切角的样式

lineCap: 端点的样式,kCALineCapButt,kCALineCapRound,kCALineCapSquare

miterLimit: 表示的是最大斜接长度,但只又lineJoin属性设置成了尖角(kCALineJoinMiter)的时候才会有效,角度如果过小的话尖角过长的部分会被削掉,会削城切角的形式即(kCALineJoinBevel)

lineDashPattern: 虚线设置,为一个数组,数组中奇数位实线长度,偶数位带遍空白长度(注意:这里的奇数,偶数以数组的第一个元素索引为1计算)

lineDashPhase: 虚线开始的位置,可以使用此属性做一个滚动的虚线框

如下所示,绘制一个虚线的圆角矩形

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:
     CGRectMake(100, 100, 200, 100) cornerRadius:15];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = 3;
layer.strokeColor = [UIColor whiteColor].CGColor;
layer.fillColor = [UIColor grayColor].CGColor;
layer.lineDashPattern = @[@6,@6];
layer.path = path.CGPath;
[self.layer addSublayer:layer];

image.png

CAGradientLayer

CAGradientLayer为一个颜色渐变的layer类,一般伴随着 CAShapeLayer 使用,其新增了下面几个属性

colors: 在Layer中现实的几种颜色并完成完美过渡,和 CAShapeLayer 的 path 一样,colors是CAGradientLayer特殊属性的起点,设置一组颜色后,系统会自动在其中过度

locations: 颜色区间分布比例,默认为线性均匀分布,取值范围为0~1递增,决定着不同颜色的分布范围,一般来说其中的元素个数应与colors中的元素个数相同,不同时系统会自行处理分布规则

例如:设置 gradientLayer.locations = @[@0.1, @0.2, @0.7]; 也就是说三个颜色的占用区间分别如此 注意了这个locations点的比例是按照startPoint和endPoint得区间来控制比例的

startPoint endPoint: startPoint决定了变色范围的起始点,endPoint决定了变色范围的结束点,两者的连线决定变色的趋势:,也就是所从第一个点的位置开始往另外一个方向扩散(变换)颜色,默认从视图左上角到右下角,取值范围为0~1

    
    NSArray *colorsAry = @[(id)[UIColor redColor].CGColor, (id)[UIColor purpleColor].CGColor, (id)[UIColor greenColor].CGColor];
    CAGradientLayer *layer = [CAGradientLayer layer];
    layer.position = CGPointMake(200, 240);
    layer.bounds = CGRectMake(0, 0, 240, 240);
    layer.locations = @[@0.1, @0.2, @0.7];
    layer.colors = colorsAry;
    layer.startPoint = CGPointMake(0, 0);
    layer.endPoint = CGPointMake(1, 1);
    
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, 200, 200)];
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor whiteColor].CGColor;//必须要给一个颜色,不然不显示
    shapeLayer.path = path.CGPath;
    shapeLayer.lineWidth = 10;
    layer.mask = shapeLayer;
    [self.layer addSublayer:layer];

image.png

CATextLayer

CATextLayer 主要负责文本的绘制,UILabel的效果他基本上都能做到,不仅能显示普通文字,还能显示富文本,唯一不足的就是,赋值内容需要转化,包括 fontcolor等,另外注意 contentsScale的设置

CATextLayer *textLayer = [CATextLayer layer];

//注意一定设置 scale,其默认为1,设置跟随系统的 scale,否则,retain屏上看起来会很模糊
textLayer.contentsScale = [UIScreen mainScreen].scale;

UIFont *font = [UIFont systemFontOfSize:15];
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;

CGFontRelease(fontRef);

textLayer.string = @"一起来学习 coreAnimation 呀!";

上面便是普通的赋值了,看起来很复杂,下面是富文本的赋值,看起来更加繁琐

NSMutableAttributedString *string = nil;
string = [[NSMutableAttributedString alloc] initWithString:text];

CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFloat fontSize = font.pointSize;
CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);

NSDictionary *attribs = @{
    (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
};
[string setAttributes:attribs range:NSMakeRange(0, [text length])];
attribs = @{
    (__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
    (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
};
[string setAttributes:attribs range:NSMakeRange(1, 2)];

CFRelease(fontRef);

textLayer.string = string;

CATransformLayer

CATransformLayer主要是用来构建 3d图形的的,平时我们用的组件基本上都是 2d 组件,对其很是陌生,在特殊情况,可能会用到它

另外一个特殊的地方就是 CATransformLayer不能自己显示东西,其需要依靠子layer 来拼接显示内容,下面通过制作一个 立方体 来演示其使用

- (void)setCubeTransformLayer {
    //这个layer相当于一个立方体容器,在里面添加layer的话可以拥有一些不错的3d效果,配合CATransform3D使用更佳
    //我们可以根据这个类创建各种各样的效果
    layer = [CATransformLayer layer];
    //创建子layer
    //上
    CATransform3D transform = CATransform3DMakeTranslation(0, -50, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor blueColor]]];
    //下
    transform = CATransform3DMakeTranslation(0, 50, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor grayColor]]];
    //左
    transform = CATransform3DMakeTranslation(-50, 0, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor yellowColor]]];
    //右
    transform = CATransform3DMakeTranslation(50, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor brownColor]]];
    //前
    transform = CATransform3DMakeTranslation(0, 0, 50);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor redColor]]];
    //后
    transform = CATransform3DMakeTranslation(0, 0, -50);
    transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
    [layer addSublayer:[self getSublayerWithTransform:transform color:[UIColor purpleColor]]];
    
    layer.position = CGPointMake(self.center.x, self.center.y);
    layer.transform = CATransform3DIdentity;
    [self.layer addSublayer:layer];
}

#pragma mark --获取某一面的layer
- (CALayer *)getSublayerWithTransform:(CATransform3D)transform color:(UIColor *)color {
    CALayer *sublayer = [CALayer layer];
    //一定要注意,position不要乱动不然这个旋转中心点也会跟着变动
    sublayer.frame = CGRectMake(-50, -50, 100, 100);
    sublayer.transform = transform;
    sublayer.backgroundColor = color.CGColor;
    return sublayer;
}

image.png

CAReplicatorLayer

CAReplicatorLayer复制图层, 也是一个比较好玩的 layer 图层,通过其特性,配合动画,可以比较简单的制作出许多比较有趣的东西

CAReplicatorLayer主要增加了下面几个方法:

instanceCount:复制的 instance 数量

instanceTransform: 复制的 instance,相对于复制的前面一个 instance位置变化,旋转时,旋转中心点在 CAReplicatorLayer中心点

instanceDelay:复制的时候延迟时间间隔(s),配合动画,可以达到意想不到的效果

如下所示设置的简易的电台波动效果,只需要通过平移的方式复制,便可以出现下面的复制图层,通过动画可以使其跳动起来(可以使用案例demo尝试)

//电台波动
CAReplicatorLayer *layer = [CAReplicatorLayer layer];
layer.position = CGPointMake(self.bounds.size.width/2, 100);
layer.bounds = CGRectMake(0, 0, self.bounds.size.width-120, 200);
[self.layer addSublayer:layer];
//设置子layer的基本样式
CALayer *subLayer = [CALayer layer];
subLayer.position = CGPointMake(2.5, 50);
subLayer.bounds = CGRectMake(0, 0, 5, 40);
subLayer.backgroundColor = [UIColor redColor].CGColor;
[layer addSublayer:subLayer];
//添加动画
CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
baseAnimation.fromValue = @1;
baseAnimation.toValue = @0.3;
baseAnimation.duration = 0.1;
baseAnimation.repeatCount = MAXFLOAT;
baseAnimation.autoreverses = YES;
[layer addAnimation:baseAnimation forKey:@"subLayerPosition"];
//开始设置复制方式
layer.instanceCount = 8;
layer.instanceTransform = CATransform3DTranslate(CATransform3DIdentity, 40, 0, 0);
layer.instanceDelay = 0.2;

image.png

下面是利用旋转效果制造出来的一圈效果,通过设置延迟,配合动画,则可以形成类似旋转的效果(可以查看案例)

//旋转动画
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.position = CGPointMake(self.bounds.size.width/2, 250);
replicatorLayer.bounds = CGRectMake(0, 0, 300, 300);
[self.layer addSublayer:replicatorLayer];
//注意这个点是相对于复制layer的基础点,可以通过控制它的位置来进一步控制动画的位置
CALayer *rLayer = [CALayer layer];
rLayer.position = CGPointMake(replicatorLayer.bounds.size.width/2+20, 20);
rLayer.bounds = CGRectMake(0, 0, 40, 40);
rLayer.cornerRadius = 20;
rLayer.backgroundColor = [UIColor blueColor].CGColor;
[replicatorLayer addSublayer:rLayer];
rLayer.transform = CATransform3DScale(CATransform3DIdentity, 0, 0, 0);
//scale变换动画
CABasicAnimation *baseA = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
baseA.fromValue = @1;
baseA.toValue = @0.1;
baseA.duration = 0.5;
baseA.autoreverses  = YES;
baseA.repeatCount = MAXFLOAT;
[rLayer addAnimation:baseA forKey:@"rLayerScale"];
//设置复制方式
replicatorLayer.instanceCount = 15;
replicatorLayer.instanceDelay = 0.05;
//注意旋转的点是replicatorLayer的position
replicatorLayer.instanceTransform = CATransform3DRotate(CATransform3DIdentity, M_PI*2/15, 0, 0, 1);

image.png

此外还可以利用此效果制造出想要的结果,案例中还有其他效果

CAScrollLayer

CAScrollLayer其使用就和 UIScrollView类似,平时也不太建议使用,使用方式如下所示,如果没有很多交互,为了减少卡顿,也许其为最后的选择了

使用方式如下所示

layer = [CAScrollLayer layer];
UIImage *image = [UIImage imageNamed:@"timg4.jpg"];
layer.contents = (__bridge id _Nullable)(image.CGImage);
layer.scrollMode = kCAScrollBoth;
//    layer.position = self.center;
layer.frame = CGRectMake(0, 0, image.size.width, image.size.height);
self.contentSize = layer.bounds.size;
[self.layer addSublayer:layer];

CATiledLayer

CATiledLayer为超大图优化加载方案,其一般表现为,滑动时按需动态加载局部图片内容,以此方式优化大图加载,例如地图这种,可能那么清晰的图片不可以一次全加载完毕,只能根据访问的位置以及高度,来进行部分图片加载(地图实际也要比这个复杂的多,也不一定会用到这个),至于平时的照相机照的图片,一般根本用不需要用到这个,其他普通优化方案即可

下面介绍一下它的使用:

使用 CATiledLayer需要实现CALayerDelegate协议,一般配合 UIScrollView使用,以实现图片按需加载,从而优化用户使用体验

注意:且注意该方法是并行绘制的,使用时需要注意线程安全,使用时需要注意 contentScale参数,其会影响图片裁剪时的显示,默认为 1 倍,如果设置为 3, 那么图片显示会缩小

- (void)initLay {
    CATiledLayer *layer = [CATiledLayer layer];
    layer.frame = CGRectMake(0, 0, self.view.bounds.size.width*6,
        self.view.bounds.size.height*6);
    layer.tileSize = self.view.bounds.size;
    layer.delegate = self;
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    scrollView.tag = 1001;
    scrollView.backgroundColor = [UIColor whiteColor];
    scrollView.bounces = NO;
    [self.view insertSubview:scrollView belowSubview:[self.view viewWithTag:100]];
    scrollView.contentSize = layer.bounds.size;
    [scrollView.layer addSublayer:layer];
}
//实现CALayerDelegate协议,根据滑动的位置,来确定加载那张图片
//图片要事先根据滑动位置,以二维坐标方式命名,以方便加载
//且注意该方法是并行调用绘制的,使用时需要注意线程安全
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx {
    CGRect bounds = CGContextGetClipBoundingBox(ctx);
    NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
    NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
    //
    NSString *imageName = [NSString stringWithFormat: @"%@_%@", @(x), @(y)];
    NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
    UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
    //
    UIGraphicsPushContext(ctx);
    [tileImage drawInRect:bounds];
    UIGraphicsPopContext();
}

image.png image.png

CAEmitterLayer

CAEmitterLayer粒子动画,苹果引入其作为一个高性能的粒子引擎,通常被用来创建实时例子动画如:烟雾、火、雨、雪等效果。

CAEmitterCell:粒子动画复用基本图层,可以用来定制各种效果的粒子,性能很高

CAEmitterLayer:为粒子CAEmitterCell的容器,用来配合显示粒子效果

因此CAEmitterLayer 需要 CAEmitterCell相互搭配才能做出想要的效果,其功能类似于 UITableViewUITableViewCell

下面介绍下其常用属性:

1、CAEmitterLayer 粒子动画容器图层

emitterPosition: 初始的中心点位置,以此点为中心点向别处发散

emitterSize: 容器大小

birthRate: 发射速度

emitterShape: 发射粒子形状,kCAEmitterLayerPointkCAEmitterLayerLinekCAEmitterLayerRectanglekCAEmitterLayerCuboidkCAEmitterLayerCirclekCAEmitterLayerSphere,分别为点、线、矩形、立方体、圆形、球

emitterMode: 发射模式,kCAEmitterLayerPointskCAEmitterLayerOutlinekCAEmitterLayerSurfacekCAEmitterLayerVolume,分别为点模式,轮廓模式,表面模式,3d模式

lifetime: 粒子生存周期,时间过了逻辑上会销毁,实际可能会被复用到其他粒子

2、CAEmitterCell 发射出去的粒子

alphaRange:一个粒子的颜色alpha能改变的范围;

alphaSpeed:粒子透明度在生命周期内的改变速度;

birthrate:粒子参数的速度乘数因子;每秒发射的粒子数量

blueRange:一个粒子的颜色blue 能改变的范围;

blueSpeed:粒子blue在生命周期内的改变速度;

color:粒子的颜色

contents:是个CGImageRef的对象,既粒子要展现的图片;

contentsRect:应该画在contents里的子rectangle:

emissionLatitude:发射的z轴方向的角度

emissionLongitude:x-y平面的发射方向

emissionRange;周围发射角度

emitterCells:粒子发射的粒子

enabled:粒子是否被渲染

greenrange: 一个粒子的颜色green 能改变的范围;

greenSpeed: 粒子green在生命周期内的改变速度;

lifetime:生命周期

lifetimeRange:生命周期范围      lifetime= lifetime(+/-) lifetimeRange

magnificationFilter:不是很清楚好像增加自己的大小

minificatonFilter:减小自己的大小

minificationFilterBias:减小大小的因子

name:粒子的名字

redRange:一个粒子的颜色red 能改变的范围;

redSpeed:粒子red在生命周期内的改变速度;

scale:缩放比例:

scaleRange:缩放比例范围;

scaleSpeed:缩放比例速度:

spin:子旋转角度

spinrange:子旋转角度范围

velocity:速度

velocityRange:速度范围

xAcceleration:粒子x方向的加速度分量

yAcceleration:粒子y方向的加速度分量

zAcceleration:粒子z方向的加速度分量

emitterCells:粒子发射的粒子

基本属性介绍完了,下面做一个下雪的案例吧(下的是特殊的雪😂)

CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = CGPointMake(self.bounds.size.width/2, -20);//f
emitterLayer.emitterSize = self.bounds.size;
emitterLayer.birthRate = 1.0;//发射速度
emitterLayer.emitterShape = kCAEmitterLayerLine;
emitterLayer.emitterMode = kCAEmitterLayerSurface;//发射模式
emitterLayer.lifetime = 1.0;//粒子生存周期
//下面设置了两种粒子
CAEmitterCell *rain = [CAEmitterCell emitterCell];
rain.birthRate = 5;//每秒发射粒子数
rain.velocity = 10;//初始速度
rain.velocityRange = 10;//速波动范围
rain.yAcceleration = 100;//y轴方向加速度
//rain.xAcceleration = 30;//x轴方向加速度
rain.color = [UIColor whiteColor].CGColor;//颜色,可被内容忽略
rain.contents = (__bridge id _Nullable)([UIImage imageNamed:@"chezi"].CGImage);//内容
//rain.spin = 0.2*M_PI;//旋转速度
//rain.spinRange = 0.5*M_PI;//旋转速度波动范围
rain.lifetime = 5;//粒子存在周期,不设置默认为0,也就是不显示,单位秒
rain.scale = 0.5;//内容缩放比例
rain.scaleRange = 0.3;//内容缩放比例波动范围

CAEmitterCell *snowflake = [CAEmitterCell emitterCell];
snowflake.birthRate = 5;
snowflake.velocity = 10;
snowflake.lifetime = 5;
snowflake.velocityRange = 5;
snowflake.yAcceleration = 60;
//snowflake.xAcceleration = 30;
snowflake.contents = (__bridge id _Nullable)([UIImage imageNamed:@"weixin"].CGImage);
snowflake.color = [UIColor whiteColor].CGColor;
snowflake.scale = 0.5;
snowflake.scaleRange = 0.4;

emitterLayer.emitterCells = @[rain,snowflake];//添加的多种粒子

[self.layer addSublayer:emitterLayer];

image.png

AVPlayer

听名字就知道是一个视频播放器的 layer,需要搭配 AVPlayer使用,使用如下所示

AVPlayer *player = [AVPlayer playerWithURL:
    [NSURL fileURLWithPath:path isDirectory:NO]];
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:player];
layer.backgroundColor = [UIColor whiteColor].CGColor;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
layer.position = self.center;
layer.bounds = CGRectMake(0, 0, 300, 200);
[self.layer addSublayer:layer];
[player play];

CAEAGLLayer

要处理高性能图形绘制,必要时就是OpenGL,应该说它应该是最后的杀手锏,至少对于非游戏的应用来说是的,相比与其他框架的使用,OpenGL非常之复杂

苹果引入了一个新的框架叫做GLKit,它去掉了一些设置OpenGL的复杂性,提供了一个叫做CLKView的UIView的子类,帮你处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要你用CAEAGLLayer完成,它是CALayer的一个子类,用来显示任意的OpenGL图形。

尽管如此,OpenGL的顶点、着色器相关等代码都需要额外编写,因此使用成本很高,这里也不多介绍,需要的或可以学习完 OpenGL 在了解与其相关文档

CATransform3D

介绍 Animation之前先介绍下基础变换,其在 layerAnimation都会使用

以下三个均忽略第一个参数,第一个参数都表示的是要变换的图形的transfrom3d值

后面的参数均表示的是沿着 x、y、z轴变化

移动
对于移动,相对于x y z轴来说正数分别是向右、下、外,负数是向左、上、内
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz)

缩放
对于缩放,参数都是大于零的,01表示的是缩小,大于1表示的是放大,即倍数关系,相对于x y z轴来说分别是沿着定点分别向左右、上下、内外缩放处理
CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz)

旋转
对于旋转分别angle是代表旋转的弧度,注意不要大于M_PI否则视为无效
后面的参数为零或者1,前面的度数为正表示顺时针旋转,负数表示逆时针旋转
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z)

测试案例

_testView.layer.transform = CATransform3DTranslate(_testView.layer.transform, 0, 200, 20);

动画 Animation

动画所寄托的 layer 已经介绍完了,Core Animation 最有趣的部分来,下面就介绍下 Animation 系列的动画 api,其类之间的继承关系如下所示(这张图也忘了哪里搞来的了,自己看了一下确实是如此,就懒得画了)

7c802bca-c74c-4857-bfda-41c953941065.png

下面介绍下可以用来做属性动画layer属性,后面会用到,这里提一下

opacity 透明度
backgroundColor 背景颜色
cornerRadius 圆角
borderWidth 边框宽度
contents 内容
shadowColor 阴影颜色
shadowOffset 阴影偏移量
shadowOpacity 阴影透明度
shadowRadius 阴影圆角
...
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
...
position 位置
position.x
position.y
...
bounds 
bounds.size
bounds.size.width
bounds.size.height
bounds.origin
bounds.origin.x
bounds.origin.y
...
...
以及CALayer子类对应的各个属性(比如CAShapeLayer的path)

CAMediaTiming

动画的基本协议,定义了时间,速度,重复次数等

beginTime:用来设置动画延时,若想延迟1秒,就设置为CACurrentMediaTime()+1,其中CACurrentMediaTime()为图层当前时间

duration:动画的持续时间

speed:动画速率,决定动画时间的倍率。当speed为2时,动画时间为设置的duration的1/2

timeOffset:动画时间偏移量。比如设置动画时长为3秒,当设置timeOffset为1.5时,当前动画会从中间位置开始,并在到达指定位置时,走完之前跳过的前半段动画

repeatCount:动画的重复次数

repeatDuration:动画的重复时间

autoreverses:动画由初始值到最终值后,是否反过来回到初始值的动画。如果设置为YES,就意味着动画完成后会以动画的形式回到初始值

fillMode:决定当前对象在非动画时间段的行为.比如动画开始之前,动画结束之后

另外,不只是CAAnimation系列遵循CAMediaTiming协议,之前学习CALayer的时候也会发现它也遵循CAMediaTiming协议,在一定程度上我们可以通过控制layer本身的协议属性来控制动画节奏

CAAnimation

CAAnimation 核心动画基础类,不能直接使用。除了CAMediaTiming协议中的方法,增加了CAAnimationDelegate的代理属性等

timingFunction:控制动画的节奏。系统提供的包括:kCAMediaTimingFunctionLinear (匀速)kCAMediaTimingFunctionEaseIn (慢进快出)kCAMediaTimingFunctionEaseOut (快进慢出)kCAMediaTimingFunctionEaseInEaseOut (慢进慢出,中间加速)kCAMediaTimingFunctionDefault (默认),当然也可通过自定义创建CAMediaTimingFunction

delegate:代理

removedOnCompletion:是否让图层保持显示动画执行后的状态,默认为YES,也就是动画执行完毕后从涂层上移除,恢复到执行前的状态,如果设置为NO,并且设置fillModekCAFillModeForwards,则保持动画执行后的状态,否则执行完毕后会自动恢复

CAPropertyAnimation

属性动画,针对layer的可动画属性进行效果设置,不可直接使用

keyPathCALayer的某个属性名,并通过这个属性的值进行修改,达到相应的动画效果

additive:属性动画是否以当前动画效果为基础,默认为NO

cumulative:指定动画是否为累加效果,默认为NO

valueFunction:此属性配合CALayertransform属性使用

CABasicAnimation

基础动画,通过keyPath对应属性进行控制,需要设置fromValue以及toValue

fromValuekeyPath相应属性的初始值

toValuekeyPath相应属性的结束值

byValue:在不设置toValue时,toValue = fromValue + byValue,也就是在当前的位置上增加多少

动画的一些属性以及代理协议,上案例介绍

- (void)touchesBegan:(NSSet<UITouch *> *)touches 
    withEvent:(UIEvent *)event {
    CGPoint position = [[touches anyObject] locationInView:self];
    //做一个简单动画
    //这个keypath其实是设置layer里面的属性,通过开始和结束等值,通过animation来创建补间动画,
    //其实更改的fromValue和toValue就是更改的keypath中的属性值
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.toValue = [NSValue valueWithCGPoint:position];//设置目标地点
    animation.delegate = self; //设置代理
    animation.beginTime = CACurrentMediaTime()+0;//设置初始时间,这里延迟0秒
    animation.duration = 3;
    //设置动画节奏,也就是动画的方式,只是一些参考
    animation.timingFunction = [CAMediaTimingFunction 
        functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//    animation.speed = 0.1;
     //动画结束后是否移除动画图层,要配合下面的属性使用可以保留动画图层
//    animation.removedOnCompletion = NO;
    //定义动画以外的处理方式,可以不还原动画效果
//    animation.fillMode = kCAFillModeForwards;
    //默认为NO,完成动画后是否以动画的形式返回,配合repeat使用更恰当
//    animation.autoreverses = YES;
//    animation.timeOffset = 0.1;//动画的偏移时间,不太建议使用
    //这个是通过kvc开保存结果的值,用来直接取出来通过事务来改变初始图层位置的的
    [animation setValue:[NSValue valueWithCGPoint:position] forKey:@"positionToEnd"];
    //加入动画,key设置一个唯一值,可以方便管理
    [layer addAnimation:animation forKey:NSStringFromSelector(_cmd)];
}

#pragma mark --停止动画后的回调方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    //开始一个事务
    [CATransaction begin];
    [CATransaction setDisableActions:YES];//在事务中关闭隐式效果
    layer.position = [[anim valueForKey:@"positionToEnd"] CGPointValue];
    [CATransaction commit];
}

设置一个简单的缩放动画案例

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.toValue = @(0.1);
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.autoreverses = YES;
[layer addAnimation:animation forKey:@"positionLayer"];

CASpringAnimation

弹性动画,很容易联想到弹簧,其带有初始速度以及阻尼指数等物理参数的属性动画,我们可以把它看成在不绝对光滑的地面上,一个弹簧拴着别小球,这样有利于理解其属性

mass:小球质量,影响惯性

stiffness:弹簧的劲度系数

damping:阻尼系数,地面的摩擦力

initialVelocity:初始速度,相当于给小球一个初始速度(可正可负,方向不同

settlingDuration:结算时间,根据上述参数计算出的预计时间,相对于你设置的时间,这个时间比较准确

使用案例演示

CASpringAnimation *animation = animation = [CASpringAnimation 
    animationWithKeyPath:@"transform.scale"];
animation.toValue = @(0.1);
animation.duration = 1;
animation.damping = 2;
animation.mass = 1;
animation.initialVelocity = 10;
//使用自动计算出来的时间,这个相对更加准确
//animation.duration = animation.settlingDuration;
animation.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.autoreverses = YES;//自动还原
[layer addAnimation:animation forKey:@"positionLayer"];

CAKeyframeAnimation

关键帧动画,同样通过keyPath对应属性进行控制,但它可以通过values或者path进行多个阶段的控制

values -> 关键帧组成的数组,动画会依次显示其中的每一帧

path -> 关键帧路径,动画进行的要素,优先级比values高,但是只对CALayeranchorPointposition起作用

keyTimes -> 每一帧对应的时间,如果不设置,则各关键帧平分设定时间

timingFunctions -> 每一帧对应的动画节奏

calculationMode -> 动画的计算模式,系统提供了对应的几种模式

tensionValues -> 动画张力控制

continuityValues -> 动画连续性控制

biasValues -> 动画偏差率控制

rotationMode -> 动画沿路径旋转方式,系统提供了两种模式

下面是制造轨迹动画的案例,其主要设置的是 path

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
    CGRectMake(40, 40, self.bounds.size.width-80, self.bounds.size.height-80)];
animation.path = path.CGPath;  //设置路径,一般使用贝塞尔曲线绘制路线,可以制造轨迹动画
animation.duration = 3;
//animation.autoreverses = YES;//自动还原
animation.repeatCount = 10;
//animation.duration = animation.settlingDuration;//使用自动计算出来的时间,这个相对更加准确
animation.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[layer addAnimation:animation forKey:@"positionLayer"];

下面是抖动案例,抖动动画设置的是 values

CAKeyframeAnimation *animation = animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
    CGRectMake(40, 40, self.bounds.size.width-80, self.bounds.size.height-80)];
animation.path = path.CGPath;
animation.duration = 3;
animation.repeatCount = 10;
//animation.duration = animation.settlingDuration;//使用自动计算出来的时间,这个相对更加准确
animation.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[layer addAnimation:animation forKey:@"positionLayer"];

CATransition

转场动画,系统提供了一些效果,此外还可以用来关闭layer隐式动画

type -> 转场动画类型,kCATransitionFadekCATransitionMoveInkCATransitionPushkCATransitionReveal,分别为渐变、移入、推进、揭开

subtype -> 转场动画方向,kCATransitionFromRightkCATransitionFromLeftkCATransitionFromTopkCATransitionFromBottom

startProgress -> 动画起点进度(整体的百分比)

endProgress -> 动画终点进度(整体的百分比)

filter -> 自定义转场

//这是一个帧动画,也就是说里面的动画是连续的,一个效果可以做几种样式
NSArray<NSString *> *imageAry = @[@"yuan",@"suo",@"wancheng",@"cheliang",@"duigou"];
CATransition *animation = [CATransition animation];
//values的优先级要小于path
animation.type = kCATransitionFade;
//animation.type = kCATransitionMoveIn;
//animation.type = kCATransitionPush
//animation.type = kCATransitionPush;
//animation.type = kCATransitionReveal;
//animation.type = @"rippleEffect";//水波纹效果,私有api,试一下即可
animation.type = kCATransitionFade;
animation.subtype = kCATransitionFromLeft;
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
layer.contents = (id)[UIImage imageNamed:imageAry[(++idx)%4]].CGImage;
[layer addAnimation:animation forKey:@"positionLayer"];

关闭隐式动画效果

[CATransaction begin];
[CATransaction setDisableActions:YES];//在事务中关闭layer的隐式效果
layer.x = x;
[CATransaction commit];

下面是type的一些常见类型,可以自行测测试一下

//默认四种
Fade = 1,                   //淡入淡出
Push,                       //推挤
Reveal,                     //揭开
MoveIn,                     //覆盖
//私有api
Cube,                       //立方体
SuckEffect,                 //吮吸
OglFlip,                    //翻转
RippleEffect,               //波纹
PageCurl,                   //翻页
PageUnCurl,                 //反翻页
CameraIrisHollowOpen,       //开镜头
CameraIrisHollowClose,      //关镜头

CAAnimationGroup

动画组,可以接收多个动画,方便对于复杂动画中的单一动画统一控制管理

animations -> 所有动画效果元素的数组。

案例如下所示,使用相对简单,将组合动画添加即可

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
animation.values = @[@(-M_PI_4/10), @(M_PI_4/10)];
animation.duration = 0.1;
animation.repeatCount = 10;

CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation1.toValue = @(40);
animation1.duration = 1;
animation1.repeatCount = 1;

CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation2.toValue = @(0.1);
animation2.duration = 1;
animation2.repeatCount = 1;

CAAnimationGroup *group = [CAAnimationGroup animation];//动画组最好是做一些不冲突可以叠加的效果
group.duration = 3;
group.autoreverses = NO;
group.animations = @[animation,animation1,animation2];
group.repeatCount = NSIntegerMax;
[layer addAnimation:group forKey:@"groupAnimation"];

UIBezierPath

在使用动画或者绘制图形时,会用到贝塞尔曲线,这里介绍下 UIBezierPath的使用

如下图所示,直接生成矩形、圆形、圆角矩形等,可以自己尝试一下

image.png

下面介绍下基本图形(或者不规则图形)的绘制,用到的一些方法

//将画笔移动到某一个点
- (void)moveToPoint:(CGPoint)point;
//将当前画笔从当前点,画直线到指定点
- (void)addLineToPoint:(CGPoint)point;
//画贝塞尔曲线到某个点,其中需要设置控制点的位置,控制弯曲的方向,可以自行研究测试下
- (void)addCurveToPoint:(CGPoint)endPoint 
    controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
//画贝塞尔曲线到某个点,其中需要设置控制点的位置,控制弯曲的方向,可以自行研究测试下
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
//添加圆弧
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius 
    startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
//用直线封闭路径(首尾相连)
- (void)closePath;

最后

Core Animation 好玩的那一部分就介绍到这里,此外 UIView 也有一些常见的动画,别忘了哈,快去尝试一下吧