【iOS】——浅析CALayer

文章目录

  • 一、CALayer介绍
  • 二、UIview与CALayer
    • 1.区别
    • 2.联系
  • 三、CALayer的使用
    • 1.初始化方法
    • 2.常用属性
  • 四.CALayer坐标系
    • 1.position属性和anchorPoint属性
    • 2.position和anchorPoint的关系
    • 3.position、anchorPoint和frame的关系
  • 五、CALayerDelegate
  • 六、CALayer绘图机制
    • 1.绘图流程
    • 2.绘图方法
      • 1.图层代理绘制
      • 2.自定义图层drawInContext:方法
  • 七、CALayer处理点击事件
    • 1.方法一:convertPoint:
    • 2.方法二:hitTest:
  • 八、隐式动画


一、CALayer介绍

在官方文档中CALayer是管理基于图像的内容并允许您对该内容执行动画的对象。通俗来说就是在iOS中我们能看到的所有的UIView对象例如文本框、按钮、输入框等等之所以能够显示到屏幕上就是因为该UIView对象内部有一个图层专门用来显示,也就是CALayer对象,通过访问layer属性便可以访问它的图层。

@property(nonatomic,readonly,strong)CALayer  *layer;    

每当我们创建一个UIView对象时并将其添加到视图层级后,UIKit 会为其自动创建并关联一个 CALayer(RootLayer)。当视图需要显示时,系统会触发视图的 layoutSubviews 方法(如有必要)进行布局调整,然后调用drawRect:方法进行绘图。绘制完成后,CALayer 的内容被更新,并通过渲染管线最终显示到屏幕上。也就是说UIView本身不具备显示功能,而是它内部的图层有显示功能。

二、UIview与CALayer

1.区别

  • UIView:继承自 UIResponder, 主要负责事件响应,属于基于 UIKit 框架
  • CALayer:继承自 NSObject, 负责图像渲染,属于 QuartzCore 框架

在这里插入图片描述

将图像渲染和事件响应这两个功能分别去实现而不让 UIView 具有直接具有图像渲染是因为

1.CALayer 所属的 QuartzCore 框架是可以跨平台使用的,在 iOS和MacOS 中都可以使用,但是UIView只能在iOS中使用,在MacOS中使用Application Kit,在这两个系统里,页面绘图框架是可以公用的,但是两个系统的交互方式却不相同,一个是通过触摸事件,另一个是通过鼠标和键盘。
2.UIView 的主要职责是负责接收并响应事件,而 CALayer 的主要职责是负责显示 UI。这里就遵循了软件工程中的“单一职责原则”,使得每个组件专注于自己的核心任务,提高了代码的可读性、可维护性和可扩展性。

由于CALayer只涉及控件在屏幕上的显示而没有事件响应,因此当只需要显示控件而不需要响应事件的时候可以优先选择CALayer,避免不必要的开销。

在这里插入图片描述

UIView 中有两个属性与图层相关分别是layer属性和layerClass属性:

  • layer 属性返回的是 UIView 所持有的主 Layer(RootLayer) 实例,我们可以通过其来设置 UIView中 layer 的一些属性例如阴影、圆角、边框、背景颜色等;
  • layerClass属性 则返回 RootLayer 所使用的类,我们可以通过重写该属性,来让 UIView 使用不同的 CALayer例如CAShapeLayer、CATextLayer 等等。

重写 layerClass 属性通常在 UIView 的子类中进行,如下所示:

@interface MyCustomView : UIView@end@implementation MyCustomView+ (Class)layerClass {return [MyCustomCALayerSubclass class];
}
@end

2.联系

UIView和CALayer是相互依赖的关系。UIView依赖于CALayer提供的内容,CALayer依赖UIView提供的容器来显示绘制的内容(对应的是backing store, 实际上一个bitmap类型的位图)
CALayer 本身构成了一个层次化的树形结构,这一结构与 UIView 的视图层级密切相关,它们分别可以有自己的SubLayer和SubView,并且可以向它的 RootLayer 上添加子 layer。

Layer 内部有三份layer tree,分别是:

  • layer tree(model tree):一般我们称模型树, 也就是各个树的节点的 model 信息, 比如常见的 frame,affineTransform, backgroundColor 等等, 这些 model 数据都是在开发中可以设置的, 我们任何对于view/layer 的修改都能反应在 model tree 中;
  • presentation tree:这是一个中间层,我们 APP 无法主动操作, 这个层内容是 iOS 系统在 Render Server中生成的;
  • render tree:这是直接对应于提交到 render server 上进行显示的树。

Model Tree代表CALayer的真实属性,Presentation Tree对应动画过程中的属性。无论动画进行中还是已经结束,Model Tree都不会发生变化,变化的是Presentation Tree。而动画结束后,Presentation Tree就被重置回到了初始状态。为了让其保持旋转状态,需要在加两句代码:

layer.fillMode=kCAFillModeForwards;
layer.removedOnCompletion=NO;

请添加图片描述
CALayer 是所有 layer 的基类,其派生类会有一些特定的功能,比如绘制文本的 CATextLayer、渐变效果的 CAGradientLayer 等等。种类如下图所示

请添加图片描述
我们通常见到的 layer 都是依附于一个 UIView,但是也有一些单独的 layer 不需要附加到 UIView 上,就可以直接在屏幕上显示内容,如 AVCaptureVideoPreviewLayer、CAShapeLayer 等。

三、CALayer的使用

前面提到CALayer用来进行内容的绘制和渲染,下面介绍下CALayer应该如何使用

1.初始化方法

//默认初始化方法
- (instancetype)init;
//类方法
+ (instancetype)layer;
//基于 coder 的初始化方法,从 storyboard、xib 文件或归档数据中解码恢复时,系统会使用此方法初始化 CALayer
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

2.常用属性


//宽度和高度
@property CGRect bounds;//位置(默认指中点距离父图层原点的位置,具体由anchorPoint决定)
@property CGPoint position;//锚点(x,y的范围都是0~1),用于定义图层在自身坐标系统中的旋转、缩放和倾斜等变换操作的参照点,默认值为 (0.5, 0.5),即图层的中心点。
@property CGPoint anchorPoint;//背景颜色(CGColorRef类型)
@property CGColorRef backgroundColor;//形变属性
@property CATransform3D transform;//边框颜色(CGColorRef类型)
@property  CGColorRef  borderColor;//边框宽度
@property CGFloat borderWidth;//圆角半径
@property CGFloat cornerRadius;//内容(比如设置为图片CGImageRef)
@property(retain) id contents;//不透明度
@property float opacity;//是否裁剪,如果为YES将会剪掉超出layer边框的部分(包括阴影)
@property BOOL masksToBounds;

如果要将图片作为layer的contents属性代码格式如下:

self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"123"].CGImage); // 跨框架赋值需要进行桥接

这里需要用到桥接,因为imageNamed:方法返回的是OC对象,遵循ARC规则
,而layer.contents接收的是Core Foundation对象,遵循MRC规则,这里用到__bridge 关键字就是告诉编译器进行“无内存管理语义”的类型转换。

注意:
contents 属性的类型为 id。在这种情况下,可以给 contents 属性赋予任何值,项目仍可以编译通过。但是在实践中,如果 content 的值不是 CGImage ,得到的图层将是空白的。
既然如此,为什么要将 contents 的属性类型定义为 id 而非 CGImage。这是因为在 Mac OS 系统中,该属性对 CGImage 和 NSImage 类型的值都起作用,而在 iOS 系统中,该属性只对 CGImage 起作用。

下面是一个关于layer一些常用属性的展示代码:

- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.//创建CALayer对象self.myLayer = [[CALayer alloc] init];//设置CALayer对象的位置和大小self.myLayer.frame = CGRectMake(100, 300, 100, 100);//self.myLayer.position = CGPointMake(100, 300);//self.myLayer.bounds = CGRectMake(100, 100, 100, 100);//self.myLayer.anchorPoint = CGPointMake(0.5, 0.5);//设置背景颜色self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;//设置阴影颜色self.myLayer.shadowColor = [UIColor grayColor].CGColor;//设置阴影偏移量self.myLayer.shadowOffset = CGSizeMake(10, 10);//设置阴影不透明度self.myLayer.shadowOpacity = 0.6;//设置边框宽度self.myLayer.borderWidth = 3.0;//设置边框颜色self.myLayer.borderColor = [UIColor blackColor].CGColor;//设置圆角半径self.myLayer.cornerRadius = 12;//设置是否裁剪self.myLayer.masksToBounds = NO;//设置内容self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接//将对象添加到控制器的Layer[self.view.layer addSublayer: self.myLayer];
}

运行结果如下:

请添加图片描述

需要注意的是如果将 self.myLayer.masksToBounds设置为YES,那么就会将超过layer边框的部分裁剪掉,也就是说阴影部分就会消失。

如果将self.myLayer.frame = CGRectMake(100, 300, 100, 100);替换成 self.myLayer.position = CGPointMake(100, 300); self.myLayer.bounds = CGRectMake(100, 100, 100, 100);那么测试结果会和前面一样吗

请添加图片描述

可以看到layer的位置发生了改变,这是因为frame属性改变的是layer在父视图中的位置和大小,frame表示图层左上角位于父图层原点(通常是左上角)水平方向 100 点、垂直方向 300 点的位置,而bounds属性只改变layer的大小不改变其位置,position属性改变的是layer中心点在父视图中的位置,一个是layer左上角作为参考点,一个是layer中心点作为参考点因此位置不一样。

四.CALayer坐标系

1.position属性和anchorPoint属性

  • position属性 表示了layer的anchorPoint属性设置的锚点距离父图层左上角(0,0)的位置。其计算公式如下:

position.x = frame.origin.x + anchorPoint.x * bounds.size.width ;
position.y = frame.origin.y + anchorPoint.y * bounds.size.height 。

  • anchorPoint属性设置的锚点是用于定义图层在自身坐标系统中的旋转、缩放和倾斜等变换操作的参照点,它是一个 CGPoint类型的值,其坐标范围是 [0, 1],表示图层内部坐标系统的相对位置。默认值为 (0.5, 0.5),即图层的中心点。它是相对于图层自身的坐标系统而言的,而非其父图层。这意味着 anchorPoint 的位置是基于图层的 bounds(即图层内部内容的大小)来确定的。

在这里插入图片描述

在这里插入图片描述

2.position和anchorPoint的关系

  1. 前面提到position表示了layer的anchorPoint属性设置的锚点距离父图层左上角(0,0)的位置,如果修改了anchorPoint属性的话,position是否会发生改变呢,答案是不会。
//设置CALayer对象的位置self.myLayer.frame = CGRectMake(100, 300, 100, 100);self.myLayer.anchorPoint = CGPointMake(0, 0);NSLog(@"x:%f y:%f",self.myLayer.position.x, self.myLayer.position.y);

请添加图片描述

 //设置CALayer对象的位置self.myLayer.frame = CGRectMake(100, 300, 100, 100);self.myLayer.anchorPoint = CGPointMake(1, 1);NSLog(@"self.myLayer.position.x:%f \n self.myLayer.positiony:%f",self.myLayer.position.x, self.myLayer.position.y);

在这里插入图片描述
可以看到虽然改变了anchorPoint但是position没有发生变化。

  1. 如果改变position的话,会导致 anchorPoint 的变化吗?答案也是不会。
self.myLayer.frame = CGRectMake(100, 300, 100, 100);self.myLayer.position = CGPointMake(100, 100);NSLog(@"self.myLayer.anchorPoint.x:%f \n self.myLayer.anchorPoint.y:%f",self.myLayer.anchorPoint.x, self.myLayer.anchorPoint.y);

请添加图片描述

self.myLayer.frame = CGRectMake(100, 300, 100, 100);self.myLayer.position = CGPointMake(100, 100);NSLog(@"self.myLayer.anchorPoint.x:%f \n self.myLayer.anchorPoint.y:%f",self.myLayer.anchorPoint.x, self.myLayer.anchorPoint.y);

请添加图片描述
可以看到修改了position但是anchorPoint没有改变,这是因为它是相对于图层自身的坐标系统而言的,取决于自身图层的大小。

3.position、anchorPoint和frame的关系

CALayerframe 在文档中被描述为是一个计算型属性,它是从 boundsanchorPointposition 的值中派生出来的。为此属性指定新值时,图层会更改其 position 和 bounds 属性以匹配您指定的矩形

那它们是如何决定 frame 的?根据图片可以套用如下公式:

frame.x = position.x - anchorPoint.x * bounds.size.width ;
frame.y = position.y - anchorPoint.y * bounds.size.height 。

这就解释了为什么修改 position 和 anchorPoint 会导致 frame 发生变化

因此,更改 anchorPoint 会直接影响图层在屏幕上显示的位置,除非同时调整 position 以保持视觉位置不变。假设有一个图层,其 position 为 (100, 100),anchorPoint 为默认的 (0.5, 0.5),且 bounds 为 (100, 100)。此时,图层的中心点位于父图层坐标系中的 (100, 100)。如果将 anchorPoint 更改为 (0, 0)(左上角),为了保持图层在屏幕上的视觉位置不变,需要将 position 调整为 (50, 50),这样图层的左上角仍保持在 (100, 100) 处。

注意:
如果修改了 frame 的值是会导致 position 发生变化的,因为 position 是基于父图层定义的;frame 的改变意味着它自身的位置在父图层中有所改变,position 也会因此改变。
但是修改了 frame 并不会导致 anchorPoint 发生变化,因为 anchorPoint 是基于自身图层定义的,无论外部怎么变,anchorPoint 都不会跟着变化。

五、CALayerDelegate

可以使用 delegate (CALayerDelegate) 对象来提供图层的内容,处理任何子图层的布局,并提供自定义操作以响应与图层相关的更改。如果图层是由 UIView 创建的,则该 UIView 对象通常会自动指定为图层的委托。跳转到CALayerDelegate定义发现其代理方法有以下这五个方法,且都是可选方法而非必选。所以delegate 只是另一种为图层提供处理内容的方式,并不是唯一的。UIView 的显示跟它图层委托没有太大关系。
在这里插入图片描述

  • - (void)displayLayer:(CALayer *)layer;
    当图层标记其内容为需要更新 (调用 setNeedsDisplay() 方法) 时,调用此方法。如果定义了此方法,应当在这里实现整个显示过程,通常通过设置contents属性来完成。这允许自定义图层内容的渲染逻辑。

下面是一段示例代码:

@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.//创建CALayer对象self.myLayer = [[CALayer alloc] init];//设置CALayer对象的位置self.myLayer.position = CGPointMake(200, 200);self.myLayer.bounds = CGRectMake(100, 100, 100, 100);//设置背景颜色self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;//设置阴影颜色self.myLayer.shadowColor = [UIColor grayColor].CGColor;//设置阴影偏移量self.myLayer.shadowOffset = CGSizeMake(10, 10);//设置阴影不透明度self.myLayer.shadowOpacity = 0.6;//设置边框宽度self.myLayer.borderWidth = 3.0;//设置边框颜色self.myLayer.borderColor = [UIColor blackColor].CGColor;//设置圆角半径self.myLayer.cornerRadius = 12;//设置是否裁剪self.myLayer.masksToBounds = NO;//设置内容self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接//设置代理self.myLayer.delegate = self;//将对象添加到控制器的Layer[self.view.layer addSublayer: self.myLayer];}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {self.myLayer.anchorPoint = CGPointMake(0.5, 0.5);self.myLayer.bounds = CGRectMake(100, 100, 200, 200);//更新图层[self.myLayer setNeedsDisplay];}- (void)displayLayer:(CALayer *)layer {self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"AppleLogo.png"].CGImage);
}
@end

运行结果如下:
请添加图片描述

点击后更新图层

请添加图片描述

  • - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
    - (void)displayLayer:(CALayer *)layer 一样,但是可以使用图层的 CGContext也就是上下文 来实现显示的过程
  • - (void)layerWillDraw:(CALayer *)layer;

在默认的 -display 方法执行前调用,允许代理在调用 -drawLayer:inContext: 之前配置任何影响图层内容的图层状态,比如contentsFormat(内容格式)和opaque(是否不透明)。如果代理实现了 -displayLayer: 方法,则此方法不会被调用。

  • - (void)layoutSublayersOfLayer:(CALayer *)layer;
    在默认的 -layoutSublayers 实现中调用,且在系统布局之前。当发现边界发生变化并且其 sublayers 可能需要重新排列时(例如通过 frame 改变大小),将调用此方法。注意,如果代理方法被调用,系统布局将被忽略,从而允许代理完全控制子图层的布局过程。

1

  • - (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

当图层需要对特定事件(如动画)作出响应时,由默认的 -actionForKey: 方法调用。应当返回一个遵循CAAction协议的对象来定义该事件的行为。可以返回nil表示代理没有为此事件指定行为,或者返回[NSNull null]明确表示不进行进一步的搜索,即不调用+defaultActionForKey:方法。

六、CALayer绘图机制

1.绘图流程

下图是 CALayer 在渲染之前的流程:

  1. 首先调用[UIView setNeedsDisplay][view.layer setNeedsDisplay]:来给一个视图或者其对应的图层打上脏标记表示需要重新绘制,但此时它还显示原来的内容,等到下一轮 RunLoop 修改才会生效。
  2. 接着当图层需要重新绘制时,会调用display方法,这个方法负责更新图层的内容,然后检查图层的代理(delegate)是否响应displayLayer:方法:
  3. 如果图层有一个代理,并且代理实现了displayLayer:方法 (YES),那么系统会调用这个方法来异步绘制图层的内容,这意味着可以通过实现displayLayer:方法来自定义图层的绘制过程,这个过程是异步进行的,不会阻塞主线程。
  4. 如果代理不响应displayLayer:方法(NO),则进入“系统绘制流程”。在这种情况下,系统会按照默认的方式绘制图层的内容。

请添加图片描述
下面是系统绘制的流程:

  1. 系统绘制时, 会先创建 用于存储像素数据的缓存区域backing storage(CGContextRef),我们可以理解为 CGContextRef 上下文;这个上下文是绘制的基础,所有图形、颜色等绘制指令都会在这个上下文中执行。
  2. 判断 layer 是否有 delegate,然后进入到不同的渲染分支中去,但是最后无论哪两个分支, 都有 CAlayer 上传 backing store。
  3. 如果有 delegate,则会执行 [layer.delegate drawLayer:inContext],然后在这个方法中会调用 view 的 drawRect: 方法,也就是我们重写 view 的 drawRect: 方法才会被调用到;
  4. 如果没有 delegate,会调用 layer 的 drawInContext 方法,也就是我们可以重写的 layer 的该方法,此刻会被调用到;

在这里插入图片描述

drawRect: 方法是在 CPU 执行的, 在它执行完之后, 通过 context 将数据 (通常情况下这里的最终结果会是一个 bitmap, 类型是 CGImageRef) 写入 backing store, 通过 rendserver 交给 GPU 去渲染,将 backing store 中的 bitmap 数据显示在屏幕上。

下面是异步绘制的流程

  1. 把UIView 显示的内容(包括 UILabel 的文字,UIImageView 的图片等)绘制生成的位图(bitmap)在子线程完成。
  2. 然后在回到主线程把bitmap赋值给view.layer.content属性。

在这里插入图片描述

  1. 首先在主线程中调用[AsyncDrawingView setNeedsDisplay]来标记视图需要重新绘制。
  2. 接着当视图需要重绘时,会调用[CALayer display]方法。
  3. display方法会调用[AsyncDrawingView displayLayer:]方法,这个方法会在全局队列中异步执行,也就是进行异步绘制工作。
  4. 在异步绘制工作中,使用CGContextCreate()创建一个位图上下文,并使用Core Graphic API进行绘制操作。
  5. 完成绘制后,通过CGBitmapContextCreateImage()将位图上下文转换为图片。
  6. 最后切换回主线程,调用[CALayer setContents:]方法,将绘制好的图像设置为layer的contents。

2.绘图方法

CAlayer图层绘图有两种方法,不管使用哪种方法绘制完必须调用图层的setNeedDisplay方法,例如[self.view.layer setNeedDisplay];,下面是图层绘制的两种方法:

  • 通过图层代理drawLayer:inContext:方法绘制
  • 通过自定义图层drawInContext:方法

1.图层代理绘制

通过代理方法进行图层绘图只要指定图层的代理,然后在代理对象中重写-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 方法即可。需要注意这个方法虽然是代理方法但是不用手动实现CALayerDelegate,因为CALayer定义中给NSObject做了分类扩展,所有的NSObject都包含这个方法。另外设置完代理后必须要调用图层的setNeedDisplay方法,否则绘制的内容无法显示。

下面是示例代码:

@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.//创建CALayer对象self.myLayer = [[CALayer alloc] init];//设置CALayer对象的位置self.myLayer.position = CGPointMake(200, 200);self.myLayer.bounds = CGRectMake(100, 100, 100, 100);//设置背景颜色self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;//设置阴影颜色self.myLayer.shadowColor = [UIColor grayColor].CGColor;//设置阴影偏移量self.myLayer.shadowOffset = CGSizeMake(10, 10);//设置阴影不透明度self.myLayer.shadowOpacity = 0.6;//设置边框宽度self.myLayer.borderWidth = 3.0;//设置边框颜色self.myLayer.borderColor = [UIColor blackColor].CGColor;//设置圆角半径self.myLayer.cornerRadius = 12;//设置是否裁剪self.myLayer.masksToBounds = NO;//设置代理self.myLayer.delegate = self;//将对象添加到控制器的Layer[self.view.layer addSublayer: self.myLayer];[self.myLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {CGContextSaveGState(ctx);//解决图形上文形变,图片倒立的问题CGContextScaleCTM(ctx, 1, -1);CGContextTranslateCTM(ctx, 0, -100);UIImage* image = [UIImage imageNamed:@"Apple.jpg.png"];CGContextDrawImage(ctx, CGRectMake(0, 0, 100, 100), image.CGImage);CGContextRestoreGState(ctx);
}

实现带阴影效果的圆形图片裁剪
我们知道当maskToBounds设置为YES时将会裁剪掉超过图层边框范围的部分,而阴影正是属于那部分的内容,如果要实现带阴影效果的圆形图片裁剪可以通过使用两个大小一样的图层,下面的图层负责绘制阴影,上面的图层用来显示图片

示例代码如下:

@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.//创建CALayer对象[self shadowLayerCreate];[self myLayerCreate];
}
- (void)shadowLayerCreate {CALayer* layerShadow = [[CALayer alloc]init];layerShadow.bounds = CGRectMake(100, 100, 100, 100);layerShadow.position = CGPointMake(200, 200);layerShadow.cornerRadius = layerShadow.bounds.size.width / 2;layerShadow.shadowColor = [UIColor grayColor].CGColor;layerShadow.shadowOffset = CGSizeMake(2, 1);layerShadow.borderColor = [UIColor grayColor].CGColor;layerShadow.shadowOpacity = 1;layerShadow.backgroundColor = [UIColor blackColor].CGColor;layerShadow.borderWidth = 3.0;[self.view.layer addSublayer:layerShadow];
}
- (void)myLayerCreate {self.myLayer = [[CALayer alloc] init];//设置CALayer对象的位置self.myLayer.position = CGPointMake(200, 200);self.myLayer.bounds = CGRectMake(100, 100, 100, 100);//设置背景颜色self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;//设置边框宽度self.myLayer.borderWidth = 3.0;//设置边框颜色self.myLayer.borderColor = [UIColor blackColor].CGColor;//设置圆角半径self.myLayer.cornerRadius = self.myLayer.bounds.size.width / 2;//设置是否裁剪self.myLayer.masksToBounds = YES;//设置内容self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接//设置代理self.myLayer.delegate = self;//将对象添加到控制器的Layer[self.view.layer addSublayer: self.myLayer];
}

请添加图片描述

需要注意的是先添加阴影layer再添加自己的layer负责阴影layer会覆盖在自己的layer之上。

2.自定义图层drawInContext:方法

在自定义图层中绘图时只要编写一个继承于CALayer的类然后在drawInContext:中绘图即可。要显示图层中绘制的内容也要调用图层的setNeedDisplay方法,否则drawInContext方法将不会调用。

示例代码如下:

#import <QuartzCore/QuartzCore.h>NS_ASSUME_NONNULL_BEGIN@interface CustomLayer : CALayer@endNS_ASSUME_NONNULL_END#import "CustomLayer.h"@implementation CustomLayer// 初始化方法,如果有自定义属性,需要在这里处理- (instancetype)init {self = [super init];if (self) {// 初始化自定义设置,如需要}return self;
}- (void)drawInContext:(CGContextRef)ctx {// 设置绘图颜色、线宽等属性CGContextSetRGBFillColor(ctx, 1.0, 0.0, 0.0, 1.0); // 设置填充颜色为红色CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 1.0, 1.0); // 设置描边颜色为蓝色CGContextSetLineWidth(ctx, 2.0); // 设置线宽为2.0// 自定义绘图命令CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)); // 绘制一个椭圆,充满整个图层边界CGContextDrawPath(ctx, kCGPathFillStroke); // 填充并描边路径
}@end#import <UIKit/UIKit.h>
#import "CustomLayer.h"
@interface ViewController : UIViewController<CALayerDelegate>@property (strong, nonatomic) CustomLayer *customLayer;@end
#import "ViewController.h"
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.// 创建自定义图层实例self.customLayer = [[CustomLayer alloc] init];// 设置图层的frameself.customLayer.frame = CGRectMake(50, 50, 200, 200);// 添加自定义图层到视图的layer上[self.view.layer addSublayer:self.customLayer];}

七、CALayer处理点击事件

前面提到CAlayer继承于继承自 NSObject, 而不是UIResponder类,因此本身不具备响应事件,但是依然有两种方法可以帮助我们实现捕捉并且处理CALayer的点击事件。

1.方法一:convertPoint:

@interface ViewController : UIViewController
@property (nonatomic, strong) CALayer *whiteAppleLayer;
@property (nonatomic, strong) CALayer *blackAppleLayer;@end#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view from its nib.self.whiteAppleLayer = [CALayer layer];self.whiteAppleLayer.frame = CGRectMake(100, 100, 200, 200);self.whiteAppleLayer.backgroundColor = [UIColor whiteColor].CGColor;self.whiteAppleLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"AppleLogo.png"].CGImage);[self.view.layer addSublayer:self.whiteAppleLayer];self.blackAppleLayer = [CALayer layer];self.blackAppleLayer.frame = CGRectMake(100, 300, 200, 200);self.blackAppleLayer.backgroundColor = [UIColor blackColor].CGColor;self.blackAppleLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage);[self.view.layer addSublayer:self.blackAppleLayer];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{CGPoint point = [[touches anyObject] locationInView:self.view];CGPoint whitePoint = [self.whiteAppleLayer convertPoint:point fromLayer:self.view.layer];CGPoint blackPoint = [self.blackAppleLayer convertPoint:point fromLayer:self.view.layer];if ([self.whiteAppleLayer containsPoint:whitePoint]) {NSLog(@"whiteApple");}if ([self.blackAppleLayer containsPoint:blackPoint]) {NSLog(@"blackApple");} 
}
@end

点击白色苹果layer时会打印whiteApple
请添加图片描述
点击黑色苹果layer后会打印blackApple

请添加图片描述
首先使用locationInView方法获取到点击的点在view(默认为根视图)上的坐标。接着通过convertPoint:fromLayer :方法传入一个CGPoint来转换坐标系,将在其父图层上的坐标转换为相对于图层自身的坐标,这样转换坐标系的方法还有以下几个:

//此方法用于将一个点的坐标从指定的layer坐标系转换到当前调用该方法的图层的坐标系中。
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; //与前一个方法相反,此方法将一个点的坐标从当前调用该方法的图层的坐标系转换到指定的layer坐标系中。
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; 
//转换一个矩形区域的坐标从指定的layer图层坐标系到当前调用该方法的图层坐标系中。
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
//将一个矩形区域的坐标从当前调用该方法的图层坐标系转换到指定的layer图层坐标系中。
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

得到触摸点相对于图层自身的坐标之后,调用containsPoint:方法。containsPoint:方法传入一个CGPoint类型参数,如果这个点在图层的frame内,则返回YES,否则返回NO。这样,就实现了对CALayer点击事件的处理。

为什么确定触摸点是否落在某个子图层上,就需要转换坐标系呢
因为子图层可能有自己独立的位置、缩放或旋转变换。换句话说,子图层的坐标原点与根视图或父视图的坐标原点可能不一致。为了准确判断触摸点是否在某个子图层内部,就需要将触摸点的坐标从全局坐标系(通常是窗口或根视图的坐标系)转换到该子图层的本地坐标系。这个转换过程考虑了子图层的所有平移、旋转和缩放变换。

2.方法二:hitTest:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{CGPoint point = [[touches anyObject] locationInView:self.view];CALayer *layer = [self.view.layer hitTest:point];if (layer == self.whiteAppleLayer) {NSLog(@"whiteApple");}else if (layer == self.blackAppleLayer){NSLog(@"blackApple");}
}

hitTest:同样传入一个CGPoint类型参数,但它的返回值不是BOOL类型,而是图层本身。如果点击的位置在最外层图层之外,则返回nil。

八、隐式动画

每一个UIView内部都默认关联着一个CALayer,我们可称这个Layer为RootLayer(根层),所有的非RootLayer,也就是手动创建的CALayer对象,都存在着隐式动画。
当对非RootLayer的部分属性进行修改时,默认会自动产生一些动画效果而这些属性称为AnimatableProperties(可动画属性)。这个就是隐式动画
下面是几个常见的AnimatableProperties:

        bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画。backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画。position:用于设置CALayer的位置。修改这个属性会产生平移动画。

如果想关闭隐式动画,可以通过动画事务(CATransaction)关闭。代码如下:

// 开始一个动画事务。
[CATransactionbegin];
//关闭隐式动画
[CATransactionsetDisableActions:YES];
//修改可动画属性
self.myview.layer.position= CGPointMake(10, 10);
//执行动画事务
[CATransactioncommit];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/7677.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用Jenkins完成Android项目打包

问题和思路 目前存在的问题 打包操作由开发人员完成&#xff0c;这样开发进度容易被打断。 解决问题的思路 将打包操作交测试/产品/开发人员来完成&#xff0c;主要是测试/开发。 按照以上的思路&#xff0c;那么JenkinsGradle的解决方案是比较经济的&#xff0c;实现起来…

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了

内核中哪些地方会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和…

5.7 线程

进程&#xff1a;解耦稳定&#xff0c;内容之间是不相关的&#xff0c;通信不便利&#xff0c;理论上进程的软硬件的切换时间以及创建开销非常大。--------》资源共享线程实现 线程的问题&#xff1a;本质就是不解耦&#xff0c;一个出问题别的就很有可能出问题&#xff0c;同…

Scanner中next()、nextInt()、nextLine()、hasNext()、hasNextInt()的使用方法及注意事项

目录 1、next()、nextInt()、nextLine()的使用方法及区分 2、循环时如何使用hasNext方法 3、用hasNextInt()作为判断下一个输入是否为数字需要配合next()方法使用 1、next()、nextInt()、nextLine()的使用方法及区分 三者简单定义 next()&#xff1a;此方法遇见第一个有效字符…

使用AIGC生成软件类图表

文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存&#xff1f;mermaid.liveDraw.io Mermaid可以画什么图&#xff1f;流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…

linux系统下产生Segmentation fault 与 Segmentation fault (core dumped)!!!

最近在学习的过程中&#xff0c;遇到了Segment fault&#xff08;段错误&#xff09;的问题&#xff0c;经过一番查找资料&#xff0c;学到了一些相关知识&#xff0c;这里做一个梳理&#xff0c;以防以后在遇到类似的问题&#xff0c;并且希望能够帮助到大家一丝丝&#xff01…

python中numpy库使用

array数组 生成array数组 将list转化为array数组 import numpy as np np.array([1,2],typenp.int32)其中dtype定义的是元素类型&#xff0c;np.int32指32位的整形 如果直接定义dtypeint 默认的是32位整形。 zeors和ones方法 zeros()方法&#xff0c;该方法和ones()类似&a…

有什么方便实用的成人口语外教软件?6个软件教你快速进行口语练习

有什么方便实用的成人口语外教软件&#xff1f;6个软件教你快速进行口语练习 口语能力在语言学习中占据着重要的位置&#xff0c;因为它直接关系到我们与他人进行交流和沟通的效果。为了提高口语能力&#xff0c;很多成人选择通过外教软件进行口语练习&#xff0c;这些软件提供…

GNU Radio FFT模块结合stream to vector应用及Rotator频偏模块使用

文章目录 前言一、FFT 模块应用1、stream to vector 介绍2、创建 grc 图测试3、运行结果 二、频偏模块1、Rotator 简介2、创建 grc 图测试3、运行结果 前言 写个博客记录一下自己的蠢劲儿&#xff0c;之前我想用 FFT 模块做一些信号分析的东西&#xff0c;官方的 FFT 模块必须…

营销5.0时代,企业的痛如何解?

进入营销5.0阶段之后&#xff0c;许多企业都需解决连接客户效能低下的问题。针对这个问题&#xff0c;产品经理、软件开发公司包括个人开发者&#xff0c;要怎么找到有效的“解药”&#xff1f; 营销不仅每年都在变化&#xff0c;甚至每天都在变化。 ——现代营销学之父&…

【再探】设计模式—适配器、装饰及外观模式

结构型设计模式是用于设计对象和类之间关系的一组设计模式。一共有7种&#xff1a;适配器模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式及代理模式。 1 适配器模式 需求&#xff1a;在软件维护阶段&#xff0c;已存在的方法与目标接口不匹配&#xff0c;需要个中…

论文阅读:RHO-1:Not All Tokens Are What You Need 选择你需要的 Tokens 参与训练

论文链接&#xff1a;https://arxiv.org/abs/2404.07965 以往的语言模型预训练方法对所有训练 token 统一采用 next-token 预测损失。作者认为“并非语料库中的所有 token 对语言模型训练都同样重要”&#xff0c;这是对这一规范的挑战。作者的初步分析深入研究了语言模型的 t…

记录一个练手的js逆向password

很明显 请求加密了password 全局搜索 有个加密函数(搜不到的可以搜临近的其他的关键字 或者url参数) 搜索的时候一定要仔细分析 我就没有仔细分析 我搞了好久 又是xhr又是hook的(还没hook到) 我当时也是疏忽了 我寻思这个也不是js文件 直到后来 我怎么也找不到 我就猜想 不…

代码随想录算法训练营DAY44|C++动态规划Part6|完全背包理论基础、518.零钱兑换II、377. 组合总和 Ⅳ

文章目录 完全背包理论基础完全背包问题的定义与01背包的核心区别为什么完全背包的循环顺序可以互换&#xff1f;CPP代码 ⭐️518.零钱兑换II思路CPP代码 ⭐️377. 组合总和 Ⅳ思路CPP代码 扩展题 完全背包理论基础 卡码网第52题 文章链接&#xff1a;完全背包理论基础 视频链接…

【数据结构与算法】力扣 102. 二叉树的层序遍历

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a; root [3,9,20,null,null,15,7] 输出&#xff1a; [[3],[9,20],[15,7]]示例 2&#x…

上证50etf期权到底该怎么玩?

今天期权懂带你了解上证50etf期权到底该怎么玩&#xff1f;ETF期权是一种股票市场上的金融衍生品&#xff0c;它是在交易所上市交易的期权合约&#xff0c;其标的资产是某个特定的交易所交易基金&#xff08;ETF&#xff09;&#xff0c;如上证50指数ETF或沪深300指数ETF等。 上…

Git命令Gitee注册idea操作git超详细

文章目录 概述相关概念下载和安装常见命令远程仓库介绍与码云注册创建介绍码云注册远程仓库操作关联拉取推送克隆 在idea中使用git集成add和commit差异化比较&查看提交记录版本回退及撤销与远程仓库关联 push从远程仓库上拉取&#xff0c;克隆项目到本地创建分支切换分支将…

(✌)粤嵌—2024/5/7—除自身以外数组的乘积

代码实现&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* productExceptSelf(int *nums, int numsSize, int *returnSize) {// 左乘积int l[numsSize];l[0] 1;for (int i 1; i < numsSize; i) {l[i] l[i - 1] * nums[…

Cesium学习——渲染、加载GeoJSON、调整位置

渲染概述 作者&#xff1a;当时明月在曾照彩云归 出处&#xff1a;https://www.cnblogs.com/jiujiubashiyi/p/17124717.html 1. 引言 Cesium是一款三维地球和地图可视化开源JavaScript库&#xff0c;使用WebGL来进行硬件加速图形&#xff0c;使用时不需要任何插件支持&#xf…

以中国为目标的DinodasRAT Linux后门攻击场景复现

概述 在上一篇《以中国为目标的DinodasRAT Linux后门剖析及通信解密尝试》文章中&#xff0c;笔者对DinodasRAT Linux后门的功能及通信数据包进行了简单剖析&#xff0c;实现了对DinodasRAT Linux后门心跳数据包的解密尝试。 虽然目前可对DinodasRAT Linux后门的通信数据包进…