iOS-CALayer详解与应用
view与layer的关系
view与layer的关系图.pngCALayer属性表如下
CALayer属性表.pngCALayer和UIView的区别
- UIView是UIKit的(只能iOS使用),CALayer是QuartzCore的(iOS和mac os通用)
- UIView继承UIResponder,CALayer继承NSObject,UIView比CALayer多了一个事件处理的功能,也就是说,CALayer不能处理用户的触摸事件,而UIView可以
- UIView来自CALayer,是CALayer的高层实现和封装,UIView的所有特性来源于CALayer支持
- CABasicAnimation,CAAnimation,CAKeyframeAnimation等动画类都需要加到CALayer上
其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层,在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层。
当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示。换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能
以下是具体功能代码
- 剪切图片的一部分
- (void)maskView {
int width = 80;
int height = 100;
int sapce = 3;
for(int i = 0; i < 9; i++) {
UIView *view = [[UIView alloc] init];
view.frame = CGRectMake(60 + (width + sapce) * (i%3), 80 + (height + sapce) * (i/3), width, height);
view.backgroundColor = [UIColor redColor];
//设置层的内容
view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"women"].CGImage);
//设置图片剪切的范围 [0,1] contentsRect 图层显示内容的大小和位置
view.layer.contentsRect = CGRectMake(1.0/3.0 * (i%3), 1.0/3.0 * (i/3), 1.0/3.0, 1.0/3.0);
[self.view addSubview:view];
/*
1:(0,0,1/3,1/3)
2: (1/3,0,1/3,1/3)
3: (2/3,0,1/3,1/3)
*/
}
}
裁剪图片.png
- 图层添加边框和圆角
- 剪切超过父图层的部分
- 阴影路径
- (void)shadowPath {
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(60,60, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:layer];
//1表明不透明,注意:设置阴影当前值不能为0,默认是0
layer.shadowOpacity = 1.0;
//阴影颜色
layer.shadowColor = [UIColor yellowColor].CGColor;
//创建路径
CGMutablePathRef path = CGPathCreateMutable();
//椭圆
CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 200, 200));
layer.shadowPath = path;
CGPathRelease(path);
}
阴影路径.png
- 添加阴影
// 添加阴影
- (void)addShadowPath {
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(60, saveH + 60, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:layer];
layer.shadowOpacity = 0.9;
layer.shadowColor = [UIColor yellowColor].CGColor;
//阴影偏移 ->x正 ->-x负 ,y同理
layer.shadowOffset = CGSizeMake(10, -10);
//阴影的圆角半径
layer.shadowRadius = 10;
}
image.png
- 图层内容和内容模式
- (void)addContentMode {
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(20, saveH + 20, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:layer];
//设置层内容
layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"logo"].CGImage);
//内容模式,类似于UIImageView的contentMode。默认是填充整个区域 kCAGravityResize
//kCAGravityResizeAspectFill 这个会向左边靠 贴到view的边边上
//kCAGravityResizeAspect 这个好像就是按比例了 反正是长方形
layer.contentsGravity = kCAGravityResizeAspect;
//设置控制器视图的背景图片 性能很高。 /
self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"logo"].CGImage);
}
三 CALayer的探究应用——进度条
progress.gif我们主要通过自定义Layer实现该功能,具体请参考工程中ProgressLayer
和ProgressView
实现。
四 使用CALayer的Mask实现注水动画效果
Core Animation一直是iOS比较有意思的一个主题,使用Core Animation可以实现非常平滑的炫酷动画。Core animtion的API是较高级的封装,使用便捷,使得我们免于自己使用OpenGL实现动画。
下面主要介绍如何使用CALayer的mask实现一个双向注水动画
了解CALayer的mask
@property(strong) CALayer *mask;
mask实际上layer内容的一个遮罩。如果我们把mask是透明的,实际看到的layer是完全透明的,也就是说只有mask的内容不透明的部分和layer叠加
实现思路
- flow 在View上重叠放置两个UIImageView: grayHead&greenHead,默认greenHead会遮挡住grayHead。
- 为greenHead设置一个mask,这个mask不是普通的mask,它由两个subLayer:maskLayerUp maskLayerDown组成。
- 默认情况下,subLayer都显示在mask内容之外,此时mask实际上透明的,由此greenHead也是透明的。
- 现在我们希望greenHead从左上角和右下角慢慢显示内容,那么我们只需要从两个方向为greenHead填充内容就可以了.
核心代码如下
- 创建mask
- (CALayer*)greenHeadMaskLayer {
CALayer* mask = [CALayer layer];
mask.frame = self.greenHeadImgView.bounds;
self.maskLayerUp = [CAShapeLayer layer];
self.maskLayerUp.bounds = CGRectMake(0, 0, 60.0f, 60.f);
self.maskLayerUp.fillColor = [UIColor greenColor].CGColor;
self.maskLayerUp.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(30.0, 30.0) radius:30.0f startAngle:0 endAngle:2*M_PI clockwise:YES].CGPath;
self.maskLayerUp.opacity = 0.8f;
self.maskLayerUp.position = CGPointMake(-5.0f, -5.0f);
[mask addSublayer:self.maskLayerUp];
self.maskLayerDown = [CAShapeLayer layer];
self.maskLayerDown.bounds = CGRectMake(0, 0, 60.f,60.f);
self.maskLayerDown.fillColor = [UIColor greenColor].CGColor;
self.maskLayerDown.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(30.0, 30.0) radius:30.0 startAngle:0 endAngle:2*M_PI clockwise:YES].CGPath;
self.maskLayerDown.position = CGPointMake(65.f, 65.f);
[mask addSublayer:self.maskLayerDown];
return mask;
}
- 做动画
- (void)startGreenHeadAnimation {
CABasicAnimation* animationDown = [CABasicAnimation animationWithKeyPath:@"position"];
animationDown.fromValue = [NSValue valueWithCGPoint:CGPointMake(-5.0f, -5.0f)];
animationDown.toValue = [NSValue valueWithCGPoint:CGPointMake(25.0f, 25.0f)];
animationDown.duration = duration;
animationDown.repeatCount = MAXFLOAT;
[self.maskLayerUp addAnimation:animationDown forKey:@"downAnimation"];
CABasicAnimation* animationUp = [CABasicAnimation animationWithKeyPath:@"position"];
animationUp.fromValue = [NSValue valueWithCGPoint:CGPointMake(65.f, 65.f)];
animationUp.toValue = [NSValue valueWithCGPoint:CGPointMake(35.f, 35.f)];
animationUp.duration = duration;
animationUp.repeatCount = MAXFLOAT;
[self.maskLayerDown addAnimation:animationUp forKey:@"upAnimation"];
}
小结
CALayer提供另外一种操作UI的手段,虽然它提供的API比UIView较底层,但它能提供更加丰富的功能和更高的性能(CALayer的动画是在专门的线程渲染的)。涉及到复杂且性能要求高的UI界面,CALayer的作用就比较明显了,比如AsyncDisplayKit。
通过本片文章,我们其实也能看出CALayer的一个用处,通常我们处理圆角时会直接去修改CALayer的cornerRadius,但这种做法性能比较差,尤其是放在列表里的时候,现在我们有了mask,这样我们可以直接改变layer的mask,而不会影响到图形渲染的性能。
五 为啥有了CALayer了还要UIView
UIView继承自UIResponder,主要特点是可以响应触摸事件。而CALayer实际的图层内容管理。大家干的的事情不一样,是两个东西,大家的存在互不影响,理所当然。
分析
所以,在这份理所当然的SDK的背后,蕴藏着大牛门几十年的设计智慧。当中应该能够看到很多门道。这次就UIView和CALayer来分析,就可以得出一些东西。
- 机制与策略分离
- 更多的不可变
- 各司其职
- 漏的更少
5.1 机制与策略分离
Unix内核设计的一个主要思想是——提供(Mechanism)机制而不是策略(Policy)。编程问题都可以抽离出机制和策略部分。机制一旦实现,就会很少更改,但策略会经常得到优化。例如原子可以看做是机制,而各种原子的组成就是一种策略。CALayer也可以看做是一种机制,提供图层绘制,你们可以翻开CALayer的头文件看看,基本上是没怎么变过的,而UIView可以看做是策略,变动很多。越是底层,越是机制,越是稳定。机制与策略分离,可以使得需要修改的代码更少,特别是底层代码,这样可以提高系统的稳定性。
5.2 更多的不可变
稳定给你的是什么感觉?坚固?不可形变?稳定其实就是不可变。一个系统不可变的东西越多,越是稳定。所以机制恰是满足这个不可变的因素的。构建一个系统有一个指导思想就是尽量抽取不可变的东西和可变的东西分离。水是成不了万丈高楼的,坚固的混凝土才可以。更少的修改,意味着更少的bug的几率。
5.3 各司其职
即使能力再大也不能把说有事情都干了,万一哪一天不行了呢,那就是突然什么都不能干了。所以仅仅是基于分散风险原则也不应该出现全能类。各司其职,相互合作,把可控粒度降到最低,这样也可以是系统更稳定,更易修改。
5.4 漏的更少
接口应该面向大众的,按照八二原则,其实20%的接口就可以满足80%的需求,剩下的80%应该隐藏在背后。因为漏的少总是安全的,不是吗。剩下的80%专家接口可以隐藏与深层次。比如UIView遮蔽了大部分的CALayer接口,抽取构造出更易用的frame和动画实现,这样上手更容易。
本文参考zmmzxxx的CALayer与iOS动画 讲解及使用,非常感谢。
- 如有错误,欢迎指正,多多点赞,打赏更佳,您的支持是我写作的动力。