「OC」探索CALayer:基础知识与实用技巧简要介绍
文章目录
前言
在顺利完成暑假之中的任务之后,终于可以学习一点之前一直没有时间学习的内容啦。上一片文章写到「iOS」自定义Modal转场——抽屉视图的实现,我们已经成功的实现了抽屉视图,接下来是要实现cell之中的圆角。我看到要想实现这个圆角cell,需要OC之中的图形绘制CALayer
的内容,于是我继续对CALayer
进行学习,如果篇幅过长那么会将圆角cell的实现放到后面来。
认识CALayer
CALayer
是属于Core Animation,简单说就是呈现内容和动画的层。
在iOS之中我们能够看到的基本上都是UIView,比如一个按钮,一个文本框等等…
那么这些UIView能够被显示在屏幕之中被我们看到,其实是就是由于这个UIView的layer属性(也就是CALayer对象),当这个UIView需要倍显示在屏幕之上的时候,就会通过调用drawRect:
这个方法,访问图层进行绘制,可以说UIView本身是没有显示的功能,只有它内部的层级才具有显示功能。
我们可以通过以下方法获取UIView之中的图层
CALayer *myLayer = myView.layer;
CALayer的相关属性
可以看到CALayer
有着丰富的属性可以用来:
- 调整图层的大小和位置
- 调整图层的背景颜色
- 修改图层的内容 (一个图片,或者是用 CoreGraphics 绘制的东西)
- 图层是否圆角
- 添加黑色投影
- 添加描边的边框
UIView和CALayer
区别
CAlayer
和UIView
最大的不同是CALayer
不处理用户的交互。CALayer
并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内。
UIApplication
、UIViewController
、UIView
、和所有从UIView派生出来的UIKit类(包括UIWindow
)都直接或间接地继承自UIResponder
类。在 UIResponder中定义了处理各种事件和事件传递的接口, 而 CALayer
直接继承 NSObject
,并没有相应的处理事件的接口。
如果显示出来的东西需要跟用户进行交互的话,那我们肯定要用UIView
;如果不需要跟用户进行交互,我们尽量使用`CALayer,因为它少了事件处理的功能,性能会高一些
联系
代理关系:UIView 实现了 CALayerDelegate 协议,这意味着当系统需要绘制 CALayer 的内容时,实际上是 UIView 在幕后调用相关方法进行绘制。这种设计使得开发者可以在 UIView 的上下文中定制内容绘制逻辑,同时利用 CALayer 的高效渲染能力。
属性映射:许多 UIView 的视觉属性(如背景色、边框等)实际上是对内部 CALayer 相关属性的封装。更改 UIView 的这些属性时,实际上是在更改其 CALayer 的对应属性。
创建UIView和CALayer的原因
我们都知道,UIView和CALayer为两个平行的层级关系,那么为什么要这么设计呢?
这里我直接引用大佬博客之中的内容:
原因在于要做职责分离,这样也能避免很多重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOS有UIKit和UIView,但是Mac OS有AppKit和NSView的原因。他们功能上很相似,但是在实现上有着显著的区别。
绘图,布局和动画,相比之下就是类似Mac笔记本和桌面系列一样应用于iPhone和iPad触屏的概念。把这种功能的逻辑分开并应用到独立的Core Animation框架,苹果就能够在iOS和Mac OS之间共享代码,使得对苹果自己的OS开发团队和第三方开发者去开发两个平台的应用更加便捷。
开始创建CALayer
我们先创建一个CALayer
并且把它加入到视图控制器之中,然后我们再进行相关内容的讲解
- (void)viewDidLoad {
[super viewDidLoad];
self.layer = [[CALayer alloc] init];
//背景设置
self.layer.backgroundColor = [UIColor redColor].CGColor;
//布局
self.layer.bounds = CGRectMake(0, 0, 100, 100);
self.layer.position = CGPointMake(100, 100);
self.layer.anchorPoint = CGPointMake(0, 0);
//旋转
self.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
//设置border属性
self.layer.borderWidth = 4;
self.layer.borderColor = [UIColor purpleColor].CGColor;
//裁切
self.layer.cornerRadius = 10.0;
self.layer.masksToBounds = YES;
// 将layer添加到图层
[self.view.layer addSublayer:self.layer];
}
视图层级
我们从视图层级的角度来看,其实不难发现他没有被单独作为一个模块列出
CALayers 和 Sublayers
就像 UIView
有很多 subview
一样, CALaye
r 也有它的 sublayer
。
视图树中每一个 view 的根 layer 根据视图关系, 形成相同关系的 layer 树, 同时每一个根 layer 还可以有自己的 sublayer, 如下图所示:
position与anchorPoint(锚点)
position
属性决定该控件在父控件中的位置,以父控件的左上角为原点anchorPoint
属性决定该控件上的哪个点位于position位置
若position为(100, 100),anchorPoint为(0, 0 ),如图
若position为(100, 100),anchorPoint为(0.5, 0.5),如图
当然CALayer
还是可以使用frame进行布局,只是如果使用CALayer
的属性进行布局在灵活性方面则更占优
CGImage和CGColor
CGImage
是 Core Graphics 框架中用于表示图像的类。它提供了对图像数据的低级别控制,适用于图像处理、渲染和绘制。
用途: CGImage
主要用于表示和操作位图图像数据。它是 UIImage
的底层表示形式,能够提供直接的图像数据访问和操作。
CGColor
是 Core Graphics 框架中用于表示颜色的类。它用于描述颜色的具体值,包括色彩空间、颜色分量等。
用途: CGColor
用于在 Core Graphics 和 Core Animation 中指定颜色。它是 UIColor
的底层表示形式,用于图层的背景色、边框色等。
我们不用先前我们所熟知的UIColor和UIImage是因为,他们被定义在UIKit.h的框架之中的,而CALayer
是QuartzCore框架的内容。
但是,很多情况下,可以通过UIKit对象的特定方法,得到CoreGraphics对象,比如UIImage的CGImage方法可以返回一个CGImageRef
设置旋转
利用transform属性可以设置旋转、缩放等效果
CATransform3D CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z);
参数说明
angle
: 旋转的角度,以弧度为单位。可以使用M_PI
或M_PI / 180
将角度转换为弧度。(M_PI
即为 360°)x
,y
,z
: 旋转轴的坐标分量。决定了围绕哪个轴旋转:- 如果
x
为 1,y
为 0,z
为 0,则围绕 X 轴旋转。 - 如果
x
为 0,y
为 1,z
为 0,则围绕 Y 轴旋转。 - 如果
x
为 0,y
为 0,z
为 1,则围绕 Z 轴旋转。
- 如果
裁切
另外当 layer 中的绘制内容超过其 frame 的边界时, 可以通过 masksToBounds
属性来决定是否绘制边界以外的内容. 而 view 的 clipsToBounds
属性实际就是在设置 layer 的 maskToBounds
属性. 默认情况下设置为 false
, 即要绘制超过边界的内容.
self.layer.cornerRadius = 10.0;
self.layer.masksToBounds = YES;
border属性
border
即为
//设置border属性
imageLayer.borderWidth = 2;
imageLayer.borderColor = [UIColor purpleColor].CGColor;
隐式动画
为了探究CALayer
自带的隐式动画,我自己写了一个好玩的内容
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) CALayer *layer;
@property (nonatomic, strong) UIView *views;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.layer = [[CALayer alloc] init];
self.layer.backgroundColor = [UIColor redColor].CGColor;
self.layer.bounds = CGRectMake(0, 0, 100, 100);
self.layer.position = CGPointMake(100, 100);
self.layer.anchorPoint = CGPointMake(0, 0);
self.layer.cornerRadius = 10.0;
self.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
[self.view.layer addSublayer:self.layer];
self.views = [[UIView alloc] init];
self.views.backgroundColor = [UIColor redColor];
self.views.frame = CGRectMake(50, 250, 100, 100);
[self.view addSubview:self.views];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(changeColor)
userInfo:nil
repeats:YES];
}
- (void)changeColor {
// 生成随机颜色
CGFloat red = arc4random_uniform(256) / 255.0;
CGFloat green = arc4random_uniform(256) / 255.0;
CGFloat blue = arc4random_uniform(256) / 255.0;
self.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
self.views.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
}
@end
通过动图我们可以看到,我们在没有添加任何动画的情况下CALayer
,在颜色发生变化时,会自动产生动画,以下是代码运行时的变化过程。
每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根层)。所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s.
当对非Root Layer的部分属性进行相应的修改时,默认会自动产生一些动画效果:
- bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
- backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
- position:用于设置CALayer的位置。修改这个属性会产生平移动画
自定义CALayer
重写CALayer的子类
我们可以通过创建CALayer的子类来描绘我们想要实现的layer,示例如下:
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ImageLayer : CALayer
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) UIColor *background;
@end
NS_ASSUME_NONNULL_END
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import "ImageLayer.h"
@implementation ImageLayer
- (void)drawInContext:(CGContextRef)ctx {
// Set the background color
CGContextSetFillColorWithColor(ctx, self.background.CGColor);
CGContextFillRect(ctx, self.bounds);
if (self.image) {
// Draw the image centered in the layer
CGRect imageRect;
imageRect.size = self.image.size;
imageRect.origin.x = (CGRectGetWidth(self.bounds) - imageRect.size.width) / 2;
imageRect.origin.y = (CGRectGetHeight(self.bounds) - imageRect.size.height) / 2;
CGContextDrawImage(ctx, imageRect, self.image.CGImage);
}
}
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
- (void)viewDidLoad {
[super viewDidLoad];
ImageLayer *imagelayer = [ImageLayer layer];
imagelayer.image = [UIImage imageNamed:@"back3.jpeg"];
imagelayer.background = [UIColor yellowColor];
imagelayer.frame = CGRectMake(50, 50, 200, 400);
[imagelayer setNeedsDisplay];
[self.view.layer addSublayer:imagelayer];
}
通过运行代码,我们可以获得以下CALayer
实现CALayer协议方法
//创建图层
CALayer *imageLayer = [[CALayer alloc] init];
//设置代理
imageLayer.delegate = self;
imageLayer.bounds = CGRectMake(0, 0, 100, 100);
imageLayer.position = CGPointMake(100, 200);
[imageLayer setNeedsDisplay];
[self.view.layer addSublayer:imageLayer];
实现代理方法
- (void)drawLayer:(nonnull CALayer *)layer inContext:(nonnull CGContextRef)ctx
{
//通过绘图方法绘制内容
}