OC

CALayer

Posted by sunzhongliang on May 11, 2018

CALayer

CALayer继承自NSObject,负责显示内容,和UIView的最大不同之处是不能够处理和用户的交互
每一个UIView都有个CALayer实例的图层属性,被称为backing layer,由视图负责创建并管理这个图层,以确保当子视图在层级关系中添加或被移除时,对应的关联图层也有相同的操作。

CALayer的几个重要属性

隐式动画:是Core Animation框架自动完成的动画,不需要我们手动控制。支持隐式动画的CALayer属性叫Animatable Properties,苹果在这里列出了所有Animatable Properties

CALayer的几个子类

  • CAShapeLayer
  • CAGradientLayer
  • CAEmitterLayer
  • CATransFormLayer
  • CAReplicatorLayer
  • CAScrollLayer
  • CATiledLayer
  • CATextLayer
  • CAEAGLLayer
  • AVPlayerLayer

CAShapeLayer

CAShapeLayer比父类CALayer多出来的几个属性:

  1. path
    path属性决定要在图层上画什么形状,如果路径延伸在图层边界外不会被自动裁剪
  2. fillColor
    fillColorlayerpath的内部填充颜色。
  3. strokeColor
    线颜色
  4. strokeStart、strokeEnd
    两个取值都是0~1,决定贝塞尔曲线的描边百分比,对应值的改变支持隐式动画
    UIBezierPath* bezierPath_rect = [UIBezierPath bezierPathWithRect:CGRectMake(20, 200, 100, 100)];
    bezierPath_rect.lineWidth = 10;
    CAShapeLayer* shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = bezierPath_rect.CGPath;
    shapeLayer.fillColor = [UIColor redColor].CGColor;
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;
    shapeLayer.lineWidth = 10;
    // 从0.5开始描边,描边到0.8为止
    shapeLayer.strokeStart = 0.5;
    shapeLayer.strokeEnd = 0.8;
    [self.view.layer addSublayer:shapeLayer];
    

    效果:

  5. lineWidth
    线宽
  6. miterLimit
    最大斜接长度,只有lineJoin属性为kCALineJoinMiter时miterLimit才有效,当衔接角度太小时,斜接长度就会很大,如果设置了 miterLimit并且当斜接长度大于这个值时,便会对应裁切掉多余
  7. lineCap
    线端点类型,也就是对应曲线结束的点的显示样式,
    kCALineCapButt:不绘制端点
    kCALineCapRound:圆形端点
    kCALineCapSquare:方形端点
  8. lineJoin
    连接点类型,也就是对应曲线节点的位置的显示样式。
  9. lineDashPattern
    虚线设置,为一个数组,数组中奇数位实线长度,偶数位带遍空白长度(注意:这里的奇数,偶数以数组的第一个元素索引为1计算)。
  10. lineDashPhase
    虚线开始的位置,配合定时器可以使用此属性做一个滚动的虚线框。
UIBezierPath* bezierPath_rect = [UIBezierPath bezierPathWithRect:CGRectMake(30, 50, 100, 100)];
CAShapeLayer* shapeLayer = [CAShapeLayer layer];
shapeLayer.path = bezierPath_rect.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
shapeLayer.lineWidth = 1;
shapeLayer.lineDashPattern = @[@6, @6]; //设置虚线样式
[self.view.layer addSublayer:shapeLayer];

// 创建子线程队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用之前创建的队列来创建定时器,注意定时器的生命周期
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        // 改变虚线起点
        CGFloat add = 3;
        shapeLayer.lineDashPhase -= add;
    });
});
// 开启定时器
dispatch_resume(self.timer);
  1. fillRule
    fillRule属性用于指定使用哪一种算法去判断画布上的某区域是否属于该图形“内部” (内部区域将被填充);
    对一个简单的无交叉的路径,哪块区域是“内部” 是很直观的。但是对一个复杂的路径,比如自相交或者一个子路径包围另一个子路径,“内部”的理解就不那么明了,fillRule提供两个选项来设置:
    • kCAFillRuleNonZero
      字面意思“非零”,判断规则:从该点作任意方向的一条射线,然后检测射线与图形路径的交点情况。从0开始计数,路径从左向右穿过射线则计数加1,从右向左穿过射线则计数减1。得出计数结果后,如果结果是0,则认为点在图形外部,否则认为在内部
    • kCAFillRuleEvenOdd
      字面意思“奇偶”,判断规则:要判断一个点是否在图形内,从该点作任意方向的一条射线,然后检测射线与图形路径的交点的数量。如果结果是奇数则认为点在内部,是偶数则认为点在外部
- (void)checkFillRule {
    UIBezierPath *path = [[UIBezierPath alloc] init];
    CGPoint circleCenter = self.view.center;
    // 第一个圆
    [path moveToPoint:CGPointMake(circleCenter.x + 50, circleCenter.y)];
    [path addArcWithCenter:circleCenter radius:50 startAngle:0 endAngle:2*M_PI clockwise:YES];
    // 第二个圆
    [path moveToPoint:CGPointMake(circleCenter.x + 100, circleCenter.y)];
    [path addArcWithCenter:circleCenter radius:100 startAngle:0 endAngle:2*M_PI clockwise:YES];
    // 第三个圆
    [path moveToPoint:CGPointMake(circleCenter.x + 150, circleCenter.y)];
    [path addArcWithCenter:circleCenter radius:150 startAngle:0 endAngle:2*M_PI clockwise:YES];
    
    // CAShapeLayer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    // 线的颜色
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    // 内部填充色
    shapeLayer.fillColor = [UIColor greenColor].CGColor;
    // 填充规则
    shapeLayer.fillRule = kCAFillRuleNonZero;
    //shapeLayer.fillRule = kCAFillRuleEvenOdd;
    
    shapeLayer.lineWidth = 5;
    shapeLayer.lineJoin = kCALineJoinBevel;
    shapeLayer.lineCap = kCALineCapRound;
    shapeLayer.path = path.CGPath;
    
    //add it to our view
    [self.view.layer addSublayer:shapeLayer];
}

CAGradientLayer

CAGradientLayer使用它可以生成平滑的颜色过渡。其属性如下:
1.colors
layer中显示的几种颜色并完成完美过渡,和CAShapeLayerpath一样,colorsCAGradientLayer特殊属性的起点,也就是x 显示的要素。

2.locations
颜色区间分布比例,默认为线性均匀分布。取值范围为0~1递增,一般来说其中的元素个数应与colors中的元素个数相同,不同时系统会自行处理分布规则。locations是相对于startPoint和endPoint的变化范围而言的, 设置 gradientLayer.locations = @[@(0.3),@(0.7)];

3.startPoint、endPoint
startPoint决定了变色范围的起始点,endPoint决定了变色范围的结束点,两者的连线决定变色的趋势

CAEmitterLayer

CAEmitterLayer粒子发射器layer

CATransformLayer

CATransformLayer作为父layer时,会对sublayer产生3D效果。

CAReplicatorLayer

CAReplicatorLayer用于图层复制,相同layer时使用这个可以起到节省内存的好处,比如说路径跟随小圈圈

CAScrollLayer

滑动相关的layer

CATiledLayer

CATiledLayer可以分片加载layer,比如加载地图等等

CATextLayer

CATextLayer是文本绘制的layer

CAEAGLLayer

openGL绘制相关的layer

AVPlayerLayer

可以高度定制播放器的layer

隐式动画的底层实现

CoreAnimation 使用action object去实现layer的隐式的动画的。action object实现了CAAction协议,并定义了相关的行为在layer上进行。所有的CAAnimation对象都实现了此协议,当layer的可动画的属性的值改变的时候,这些对象通常会赋值并执行。而动画的属性就是一种action

自定义action object

自定义action object,需要实现CAAction协议的方法runActionForKey:object:arguments:

- (void)runActionForKey:(NSString *)event object:(id)anObject
              arguments:(nullable NSDictionary *)dict {
    NSLog(@"event:%@", event);
    NSLog(@"anObject:%@", anObject);
    NSLog(@"arguments:%@",dict);
}

action object可以在下面几种情况下触发:

  1. layer的属性发生改变的时候,可以是任何属性,不仅仅是可动画的属性,也可以使自定义的属性。这时action的key为属性的名字。
  2. layer变为可见的,或者添加到另一个layer上时,此时action的key为kCAOnOrderIn.
  3. layer从superlayer上移除时,此时action的key为kCAOnOrderOut.
  4. 当layer进行一个transition animation时,此时action的key为kCATransition.

Core Animation寻找action object的顺序

在一个action执行之前,layer需要找到对应的action object去执行。当layer上一些事件(上面的三种)发生时,layer会调用自己的 actionForKey: 方法去寻找对应的action object

  1. 如果layer有一个delegate ,此delegate实现了actionForLayer:forKey方法,layer会调用此方法,此方法可以根据对应的key返回相应的action object,如果返回的是nil,则继续步骤2,如果返回的为NSNull对象,则立即停止搜索。
  2. layer在actions字典中去寻找。
  3. layer在style字典中去寻找(style字典中有一个actions的key,其value为一个字典,layer就是在这个字典中去寻找key对应的action object)
  4. layer调用类方法defaultActionForKey:
  5. layer进行Core Animation定义的implicit action。

以上任何一步,如果返回非 nil,则搜索停止,否则继续往下搜索。
经过一轮搜索之后,- actionForKey:(NSString *);要么返回nil 不会有动画,要么是CAAction协议的对象,CALayer 拿这个对象做动画。
但有一种特殊情况,就是返回 [NSNull null] ,由于是非 nil,则搜索停止, [NSNull null] 也会转化为 nil,导致没有动画的结果。

为什么UIView没有隐式动画?

UIView中,layer的delegate指向UIView,UIView的-actionForLayer:forKey:方法默认返回 [NSNull null],所以UIView的隐式动画才能被禁止。但在Block动画中返回 CAAnimation 对象,所以Block动画中有动画效果,测试代码如下:

NSLog(@"%@   %@", nil ,[NSNull null]); // 输出:(null)   <null>
NSLog(@"%@", [self.ddddd actionForLayer:self.ddddd.layer forKey:@"backgroundColor"]); // 输出:<null>

[UIView animateWithDuration:0.1 animations:^{
    // 输出:<CABasicAnimation: 0x600003066a20>
    NSLog(@"%@", [self.ddddd actionForLayer:self.ddddd.layer forKey:@"backgroundColor"]);
}];

参考地址

禁用隐式动画

禁用CALayer隐式动画方法有两种:
1.实现CALayerDelegate,并在-actionForLayer:forKey:返回NSNull

- (nullable id<CAAction>)actionForKey:(NSString *)event {
    return [NSNull null];
}

2.通过[CATransaction setDisableActions:YES];事物来执行

//事务
[CATransaction begin];
[CATransaction setAnimationDuration:5.f]; //重置动画时间
// [CATransaction setDisableActions:YES]; //action无效化,即关闭动画

//animation code area

[CATransaction commit];

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.