CALayer精讲

时间:2023-02-14 20:55:44

前言

CALayer包含在QuartzCore框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中。后面要学Core Animation就应该先学好Layer(层)。

我们看一下UIViewLayer之间的关系图(图片来源于网络):

CALayer精讲

我们知道,UIView有一个属性layer,这个是在视图创建时就会自动创建一个图层。想要呈现出来,就需要到Layer。层是可以放很多个子层的,也就可以实现多种多样的效果。

CALayer关键属性说明

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
// 与UIView的bounds类似的,获取或设置图层的大小
@property CGRect bounds;
 
/* The position in the superlayer that the anchor point of the layer's
* bounds rect is aligned to. Defaults to the zero point. Animatable. */
// 获取或设置在父图层中对齐位置,默认为(0,0),也就是左上角。
// 这个属性与UIView的center属性类似,不过在图层中使用的是position
// 这里关系到锚点,关于锚点在游戏世界里是非常关键的概念
// 支持隐式动画
@property CGPoint position;
 
/* The Z component of the layer's position in its superlayer. Defaults
* to zero. Animatable. */
// 层与层之间有上下层的关系,设置Z轴方向的值,可以指定哪个层在上,哪个层在下
// 支持隐式动画
@property CGFloat zPosition;
 
/* Defines the anchor point of the layer's bounds rect, as a point in
* normalized layer coordinates - '(0, 0)' is the bottom left corner of
* the bounds rect, '(1, 1)' is the top right corner. Defaults to
* '(0.5, 0.5)', i.e. the center of the bounds rect. Animatable. */
// 这个就是游戏中必须要懂的锚点,默认为(0.5, 0.5),也就是正*。
// 关于锚点的知识,当年自学cocos2d-x的时候也困扰过我一段时间,这个有专门的文章讲解的
// 大家可以查一查相关专题讲解。因为这个确实不好理解,一时说不通。
// 支持隐式动画
@property CGPoint anchorPoint;
 
/* A transform applied to the layer relative to the anchor point of its
* bounds rect. Defaults to the identity transform. Animatable. */
// 图层形变,做动画常用
// 支持隐式动画
@property CATransform3D transform;
 

温馨提示:在CALayer中很少使用frame属性,因为frame本身不支持动画效果,通常使用boundsposition代替。CALayer中透明度使用opacity表示而不是alpha;中心点使用position表示而不是center

实战点击放大移动效果

先看看效果图:

CALayer精讲

实现代码逻辑:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
#define kLayerWidth 50
 
@interface HYBMoveCircleLayerController ()
 
@property (nonatomic, strong) CALayer *movableCircleLayer;
 
@end
 
@implementation HYBMoveCircleLayerController
 
- (void)viewDidLoad {
  [super viewDidLoad];
  
  self.movableCircleLayer = [CALayer layer];
  // 指定大小
  self.movableCircleLayer.bounds = CGRectMake(0, 0, kLayerWidth, kLayerWidth);
  // 指定中心点
  self.movableCircleLayer.position = self.view.center;
  // 变成圆形
  self.movableCircleLayer.cornerRadius = kLayerWidth / 2;
  // 指定背景色
  self.movableCircleLayer.backgroundColor = [UIColor blueColor].CGColor;
  // 设置阴影
  self.movableCircleLayer.shadowColor = [UIColor grayColor].CGColor;
  self.movableCircleLayer.shadowOffset = CGSizeMake(3, 3);
  self.movableCircleLayer.shadowOpacity = 0.8;
  
  [self.view.layer addSublayer:self.movableCircleLayer];
}
 
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  CGFloat width = kLayerWidth;
  if (self.movableCircleLayer.bounds.size.width <= kLayerWidth) {
    width = kLayerWidth * 2.5;
  }
  
  // 修改大小
  self.movableCircleLayer.bounds = CGRectMake(0, 0, width, width);
  // 将中心位置放到点击位置
  self.movableCircleLayer.position = [[touches anyObject] locationInView:self.view];
  // 再修改成圆形
  self.movableCircleLayer.cornerRadius = width / 2;
}
 
@end
 

这里需要注意的是每次更新位置时,这个图层的大小和cornerRadius都需要更新,否则就不成圆形了!

通过层内容呈现图片

效果图片:

CALayer精讲

代码实现:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
- (void)drawImageWithContent {
  CALayer *layer = [CALayer layer];
  layer.bounds = CGRectMake(0, 0, kPhotoWidth, kPhotoWidth);
  layer.position = self.view.center;
  layer.cornerRadius = kPhotoWidth / 2;
  // 要设置此属性才能裁剪成圆形,但是添加此属性后,下面设置的阴影就没有了。
  layer.masksToBounds = YES;
  layer.borderColor = [UIColor whiteColor].CGColor;
  layer.borderWidth = 1;
  
  // 如果只是显示图片,不做其它处理,直接设置contents就可以了,也就不会出现
  // 绘图和图像倒立的问题了
  layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"bb"].CGImage);
  
  [self.view.layer addSublayer:layer];
}
 

如果我们只是需要显示图片到图层上,通过设置contents属性就可以了,也就不用绘图,也不会出现图像倒立的问题了。

通过层代理绘制图片


上面的内容呈现是很简单的,但是如果我们要增加其它效果呢?那就需要别的方式了。如下效果图:

CALayer精讲

代码实现:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 
- (void)drawImage {
  CALayer *layer = [CALayer layer];
  layer.bounds = CGRectMake(0, 0, kPhotoWidth, kPhotoWidth);
  layer.position = self.view.center;
  layer.cornerRadius = kPhotoWidth / 2;
  // 要设置此属性才能裁剪成圆形,但是添加此属性后,下面设置的阴影就没有了。
  layer.masksToBounds = YES;
  layer.borderColor = [UIColor whiteColor].CGColor;
  layer.borderWidth = 1;
  
//  // 阴影
//  layer.shadowColor = [UIColor blueColor].CGColor;
//  layer.shadowOffset = CGSizeMake(4, 4);
//  layer.shadowOpacity = 0.9;
  
  // 指定代理
  layer.delegate = self;
  
  // 添加到父图层上
  [self.view.layer addSublayer:layer];
  
  // 当设置masksToBounds为YES后,要想要阴影效果,就需要额外添加一个图层作为阴影图层了
  CALayer *shadowLayer = [CALayer layer];
  shadowLayer.position = layer.position;
  shadowLayer.bounds = layer.bounds;
  shadowLayer.cornerRadius = layer.cornerRadius;
  shadowLayer.shadowOpacity = 1.0;
  shadowLayer.shadowColor = [UIColor redColor].CGColor;
  shadowLayer.shadowOffset = CGSizeMake(2, 1);
  shadowLayer.borderWidth = layer.borderWidth;
  shadowLayer.borderColor = [UIColor whiteColor].CGColor;
  [self.view.layer insertSublayer:shadowLayer below:layer];
  
  // 调用此方法,否则代理不会调用
  [layer setNeedsDisplay];
}
 
#pragma mark - CALayerDelegate
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
  // 将当前上下文入栈
  CGContextSaveGState(ctx);
  
  // 注意:坐标系统与UIView的不同,这里使用的是笛卡尔积坐标系,也就是左下角为(0,0)
  // 所以,我们只要记住这点就可以很容易地变换了。
  
  // 处理图片倒立的问题
  // 默认呈现是倒立的,因此需要将形变矩阵的sy设置为-1就成了正立的了
  // 先缩放后平移也可以
// CGContextScaleCTM(ctx, 1, -1);
// CGContextTranslateCTM(ctx, 0, -kPhotoWidth);
  
  // 先向平移后旋转也可以解决倒立的问题
  CGContextTranslateCTM(ctx, kPhotoWidth, kPhotoWidth);
  CGContextRotateCTM(ctx, 3.1415926 / 180 * 180);
  
  UIImage *image = [UIImage imageNamed:@"bb"];
  CGContextDrawImage(ctx, CGRectMake(0, 0, kPhotoWidth, kPhotoWidth), image.CGImage);
  
  // 任务完成后,将当前上下文退栈
  CGContextRestoreGState(ctx);
}
 

我们需要注意,一旦设置了层的layer.masksToBoundsYES,那么阴影效果就没有了,也就是不能直接对该层设置shadow相关的属性了,设置了也没有作用。因此,这里使用另外一个图层来专门呈现阴影效果的。

我们需要将阴影层放在图片层的下面,别把图片层给挡住了。默认是不会调用代理方法的,必须要调用setNeedsDisplay方法才会回调。因此,要绘制哪个层就调用哪个层对象调用该方法。

在代理方法中,我们介绍一下相关代码。CGContextSaveGState方法是入栈操作,也就是将当前设备上下文入栈。既然有入栈,自然也得有出栈,最后一行CGContextRestoreGState就是将设备上下文出栈。我们必须明确一点,绘图是在设备上下文上操作的,因此,我们所进行的绘图操作都会是栈顶元素上的。

矩阵操作:通过CGContextDrawImage绘制的图片是倒立的,因此我们需要进行矩阵相关变换。关于矩阵变换的知识是数学知识,也是知识难点,关于此理论知识,大家可以阅读:http://www.tuicool.com/articles/Er6VNf6

我们需要明确坐标系为笛卡尔积坐标系,坐标原点在左下角。这里提供了两种解决图像倒立问题的方法。

  • 第一种:先绽放后平移
  • 第二种:先平移后旋转

对于第一种,我们先设置矩阵的sx,sy分别为1,-1,然后平移到(0, -kPhotoWidth)。对于这一种不好理解,因此,笔者提供了第二种方法,更容易理解一些。

第二种,先平移到右上角,然后再旋转180度,正好正立。

网络上的一张图片:

CALayer精讲

坐标原点在左下角,我们将倒立的图片先平移到右上角,再顺时针旋转180度正好形成正立。

CALayer精讲的更多相关文章

  1. iOS CAShapeLayer精讲

    前言 CAShapeLayer继承自CALayer,因此,可使用CALayer的所有属性.但是,CAShapeLayer需要和贝塞尔曲线配合使用才有意义. 关于UIBezierPath,请阅读文章:i ...

  2. iOS-UI控件精讲之UIView

    道虽迩,不行不至:事虽小,不为不成. 相关阅读 1.iOS-UI控件精讲之UIView(本文) 2.iOS-UI控件精讲之UILabel ...待续 UIView是所有UI控件的基类,在布局的时候通常 ...

  3. 深入Java核心 Java内存分配原理精讲

    深入Java核心 Java内存分配原理精讲 栈.堆.常量池虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同.本文将深入Java核心,详细讲解Java内存分配方面的知识. Java内存分 ...

  4. WKWebView API精讲&lpar;OC&rpar;

    WKWebView API精讲(OC) 前言 鉴于LL同志对笔者说:“能不能写个OC版本的WKWebView的使用教程?”,还积极打赏了30RMB,笔者又怎么好意思拒绝呢,于是才有了下文. 所有看到本 ...

  5. 《VC&plus;&plus; 6简明教程》即VC&plus;&plus; 6&period;0入门精讲 学习进度及笔记

    VC++6.0入门→精讲 2013.06.09,目前,每一章的“自测题”和“小结”三个板块还没有看(备注:第一章的“实验”已经看完). 2013.06.16 第三章的“实验”.“自测题”.“小结”和“ ...

  6. iOS开发——语法篇OC篇&amp&semi;高级语法精讲二

    Objective高级语法精讲二 Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和 ...

  7. iOS开发——语法篇OC篇&amp&semi;高级语法精讲

    高级语法精讲 一.NSSet.NSMutableSet集合的介绍 1)NSSet.NSMutableSet集合,元素是无序的,不能有重复的值. 2)用实例方法创建一个不可变集合对象 例如: //宏定义 ...

  8. 【C&plus;&plus;自我精讲】基础系列二 const

    [C++自我精讲]基础系列二 const 0 前言 分三部分:const用法.const和#define比较.const作用. 1 const用法 const常量:const可以用来定义常量,不可改变 ...

  9. 【C&plus;&plus;自我精讲】基础系列四 static

    [C++自我精讲]基础系列四 static 0 前言 变量的存储类型:存储类型按变量的生存期划分,分动态存储方式和静态存储方式. 1)动态存储方式的变量,生存期为变量所在的作用域.即程序运行到此变量时 ...

随机推荐

  1. Java String&period;split&lpar;&rpar;

    在java.lang包中有String.split()方法,返回是一个数组 我在应用中用到一些,给大家总结一下,仅供大家参考: 1.如果用“.”作为分隔的话,必须是如下写法,String.split( ...

  2. HDU1009

    题意:有n个房子,每个房子里都有老鼠喜欢吃的咖啡豆J[i],但是每个房子都有猫看守,老鼠现在手上有M的猫粮.可以用猫粮换咖啡豆,每只猫都有猫粮的要求F[i].老鼠得到的咖啡豆是J[i]*a%     ...

  3. C&num;使用log4net

    C#很多异步机制使程序无法使用断点调试,这时候我们就需要使用日志输出.使用log4net一定要先引用net4log这个dll,不然无法使用. 作者很懒,直接上代码吧. using System; us ...

  4. Struts2漏洞解决

    如果你也正在使用Struts2作为web层框架做开发或者做公司的送检产品,然后被告知有各种各样的Struts2漏洞,那本篇博客值得你花时间来喽上一两眼. 前端时间抽空为公司做了新一代的送检产品,为了方 ...

  5. 基于Twitter的Snowflake算法实现分布式高效有序ID生产黑科技(无懈可击)

    参考美团文档:https://tech.meituan.com/2017/04/21/mt-leaf.html Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万 ...

  6. windows2012的服务器远程桌面提示内部错误的问题解决方法

    一.问题表象 我们在OpenStack安装了windows server2012r2版本的虚拟机,在本地通过远程桌面连接时,输入账号密码后,提示连接断开或者内部错误的问题 二.解决办法 1)windo ...

  7. Django框架(七)

    15 Django组件-中间件 中间件 中间件的概念 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出.因为改变的 ...

  8. 单调队列 Monotonic Queue &sol; 单调栈 Monotonic Stack

    2018-11-16 22:45:48 一.单调队列 Monotone Queue 239. Sliding Window Maximum 问题描述: 问题求解: 本题是一个经典的可以使用双端队列或者 ...

  9. springcloud-eureka简单实现

    请参考 spring+cloud为服务实战 第三章 一.创建Eureka服务 1.使用Idea创建一个项目 结构如下: 2.pom.xml配置: <?xml version="1.0& ...

  10. linux -- 常用的20个命令

    1. ls命令 ls命令是列出目录内容(List Directory Contents)的意思.运行它就是列出文件夹里的内容,可能是文件也可能是文件夹. root@tecmint:~# ls Andr ...