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
多出来的几个属性:
- path
path
属性决定要在图层上画什么形状,如果路径延伸在图层边界外不会被自动裁剪 - fillColor
fillColor
即layer
的path
的内部填充颜色。 - strokeColor
线颜色 - 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];
效果:
- lineWidth
线宽 - miterLimit
最大斜接长度,只有lineJoin属性为kCALineJoinMiter时miterLimit才有效,当衔接角度太小时,斜接长度就会很大,如果设置了 miterLimit并且当斜接长度大于这个值时,便会对应裁切掉多余 - lineCap
线端点类型,也就是对应曲线结束的点的显示样式,
kCALineCapButt
:不绘制端点
kCALineCapRound
:圆形端点
kCALineCapSquare
:方形端点 - lineJoin
连接点类型,也就是对应曲线节点的位置的显示样式。 - lineDashPattern
虚线设置,为一个数组,数组中奇数位实线长度,偶数位带遍空白长度(注意:这里的奇数,偶数以数组的第一个元素索引为1计算)。 - 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);
- 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
中显示的几种颜色并完成完美过渡,和CAShapeLayer
的path
一样,colors
是CAGradientLayer
特殊属性的起点,也就是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
可以在下面几种情况下触发:
- 当
layer
的属性发生改变的时候,可以是任何属性,不仅仅是可动画的属性,也可以使自定义的属性。这时action的key为属性的名字。 layer
变为可见的,或者添加到另一个layer上时,此时action的key为kCAOnOrderIn.layer
从superlayer上移除时,此时action的key为kCAOnOrderOut.- 当layer进行一个
transition animation
时,此时action的key为kCATransition
.
Core Animation寻找action object的顺序
在一个action
执行之前,layer
需要找到对应的action object
去执行。当layer上一些事件(上面的三种)发生时,layer
会调用自己的 actionForKey:
方法去寻找对应的action object
- 如果layer有一个delegate ,此delegate实现了
actionForLayer:forKey
方法,layer会调用此方法,此方法可以根据对应的key返回相应的action object,如果返回的是nil,则继续步骤2,如果返回的为NSNull对象,则立即停止搜索。 - layer在actions字典中去寻找。
- layer在style字典中去寻找(style字典中有一个actions的key,其value为一个字典,layer就是在这个字典中去寻找key对应的action object)
- layer调用类方法
defaultActionForKey:
- 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] , 转载请保留原文链接.