《iOS 三问》 -- CALayer 的专用图层

CALayer 知识点脑图

各种 CALayer 子类可以满足各种界面绘制的场景。这算是 CALayer 对外提供的工具类了,用它们可以方便地绘制你想要的图形。

绘图时要注意的点:

  1. 一般要设置 layer 的 contentScale = [UIScreen mainScreen].scale;
  2. 使用传用图层可以直接添加,无需手动调用 setNeedDisplay 方法;

1 CATextLayer

Core Animation 提供了一个 CALayer 的子类 CATextLayer ,它以图层的形式包含了 UILabel 几乎所有的绘制特性,并且额外提供了一些新的特性。让你可以方便地制作一个包含你想要文本的图层。

1.1 什么要使用 CATextLayer?

CATextLayer 相较 UILabel 要更轻量级、更快些。CATextLayer 使用 Core text 渲染,速度上更快;

1.2 主要参数

CATextLayer 主要参数
1
2
3
4
5
6
7
8
9
10
11
12

· string
要输出的字符串,这个是 CATextLayer 最主要的属性

· bounds
图层的大小,忘了设这个你的 text 就无法显示了

·font, fontSize, alignmentMode
字体,字体大小,对齐方式

· foregroundColor
字体的颜色

1.3 示例:

CATextLayer 使用示例
1
2
3
4
5
6
7
8
9
10

CATextLayer *tLayer =[CATextLayer layer];
tLayer.contentsScale = [UIScreen mainScreen].scale; // 注意,这个不能忘
tLayer.string = @"CATextLayer 测试一下";
tLayer.bounds = CGRectMake(0, 0, 300, 20);
tLayer.fontSize = 14.f; // 字体的大小
tLayer.font = (__bridge CFTypeRef _Nullable)(@"HelveticaNeue-BoldItalic"); // 字体的名字 不是 UIFont
tLayer.alignmentMode = kCAAlignmentCenter; // 字体的对齐方式
tLayer.position = CGPointMake(150, 0);
tLayer.foregroundColor =[UIColor redColor].CGColor; // 字体的颜色

2 CAShapeLayer

CAShapeLayer 可以用来绘制所有能够通过 CGPath 来表示的形状。这个形状不一定要闭合,路径也不一定要不可破。

因此,CAShapeLayer 经常与 UIBezierPath 一起使用。UIBezierPath 类允许你在自定义的 View 中绘制和渲染由直线和曲线组成的路径。你可以在初始化的时候直接为你的 UIBezierPath 指定一个几何图形。
通俗点就是 UIBezierPath 用来指定绘制图形路径,而 CAShapeLayer 就是根据路径来绘图的。

使用 CAShpageLayer 同样有许多优点:

  1. 渲染快速。 CAShapeLayer 使用了硬件加速,绘制同一图形会比用 Core Graphics很多。
  2. 高效使用内存。一个 CAShapeLayer 不需要像普通 CALayer 一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  3. 不会出现像素化。当你给 CAShapeLayer 做 3D 变换时,它不像一个有寄宿图的普通图层一样变得像素化。

2.1 主要参数

CAShapeLayer 主要参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

- path
图层要绘制的路径,这个可以说是最主要的属性了。

- fillColor , strokeColor
描边色与都填充色。

- fillRule
填充规则,'non-zero' or 'even-odd'

- stokeStart , strokeEnd
绘制描边时的区域,取值为比例位置 (like anchorPoint, 0~10 是开始位置,1 为结束位置)

- lineWidth , miterLimit, lineCap , lineJoin, lineDashPhase, lineDashPattern
lineWidth: 线宽
miterLimit: 最大斜接长度 (与动画相关)。
lineCap: 线段头样式 (kCALineCapButt, kCALineCapRound ,kCALineCapSquare)。
lineJoin: 连接点样式 (kCALineJoinMiter、kCALineJoinRound、kCALineJoinBevel),
lineDashPhase: 虚线起始位置。
lineDashPattern:虚线样式模板(数组)。

2.2 示例

使用 CAShapeLayer 在黄色区域中画一个矩形
1
2
3
4
5
6
7
8
9
10
11
12
13
14

CAShapeLayer *sLayer = [CAShapeLayer layer];
sLayer.contentsScale = [UIScreen mainScreen].scale;
sLayer.frame = CGRectMake(0, 0, 150, 150);
sLayer.backgroundColor = [UIColor yellowColor].CGColor; // 设置 layer 背景色
sLayer.strokeColor = [UIColor blueColor].CGColor; // 设置描边色
sLayer.fillColor = [UIColor redColor].CGColor; // 设置填充色
sLayer.lineJoin = kCALineJoinRound;
sLayer.lineCap = kCALineCapRound;
sLayer.lineWidth = 4;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 10, 100, 100)
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(10, 10)];
sLayer.path = path.CGPath;

3 CATransformLayer

如果我们想像 Transform 那章 一样画了多个立方形,怎么去旋转他们呢?

在那一章中,我们是通过转动父 layer 的 sublayerTransform 属性来实现的,但是如果是多个立方形,就不能这样的,不然多个立方形会跟着一起转动。

那我们怎么做呢?我们可以创建一个 CATransformLayer,然后把每个立方形放到独立的 CATransformLayer 上,当要转动时,直接转动 CATransformLayer 就行了,他会对其中的子 layer 一起转动(只需要赋值给其 transform 属性),相当于 sublayerTransform.

1
2
3
4
5
6
CATransformLayer *cube = [CATransformLayer layer];

[cube addSublayer:face1]; ...

...
cube.transform = transform;

4 CAGradientLayer

渐变色图层控件,可以很方便地画出渐变色控件。

4.1 主要参数

1
2
3
4
5
6
7
8
9
10
11
12
13

- colors : array<id CGColor>
传入一个数组,规定所有的渐变色。

- locations : array<NSNumber>
渐变颜色的区间分布(就是 colos 中每个渐变颜色的起点和最后颜色的终点位置,这里用的是比例位置,参考 anchorPoint),locations 的数组长度和 colors 一致。这个属性可不设,默认是 nil,系统会平均分布颜色如果有特定需要可设置,数组设置为 0 ~ 1 之间单调递增。

- startPoint , endPoint
使用与 anchorPoint 一样的比例坐标,startPoint 默认 (0.5, 0), endPoint 默认 (0.5,1),也就是从顶边的中点,到底边的中点,从上到下渐变。

- type
绘制类型,默认 kCAGradientLayerAxial(表示按像素均匀变化)。

4.2 示例

画一个渐变的矩形
1
2
3
4
5
6
7
8
9
CAGradientLayer *gLayer = [CAGradientLayer new];
gLayer.contentsScale = [UIScreen mainScreen].scale;
gLayer.frame = CGRectMake(0, 0, 100, 100);
gLayer.colors = @[(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor];
gLayer.type = kCAGradientLayerAxial;
gLayer.locations = @[@0.0f, @1.0f];
layerFrame = [UIView new];
[layerFrame.layer addSublayer:gLayer];
layerFrame.flexSize = CGSizeMake(100, 100);

5 CAReplicatorLayer

CAReplicatorLayer 的功能是用于高效地生成许多相似的图层。

5.1 主要属性

CAReplicatorLayer 主要属性
1
2
3
4
5
6
7

· instanceCount
指定图层要重复的次数

· instanceTransform
指定一个 CATransform3D 变换,变换是逐步增加的,每个实例都是相对于前一个实现的变换!(这就是为什么最终复制体会出现在不同位置的原因)

5.2 示例 - 由重复小方块组成的圆圈

示例效果可以在我的 iOSOneDemo 中查看。

由重复小方块组成的圆圈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)addReplicatorLayerOfView:(UIView *)view
{
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 0, 300, 300);
[view.layer addSublayer:replicatorLayer];

replicatorLayer.instanceCount = 10;

// instanceTransform
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
replicatorLayer.instanceTransform = transform;

// color shift
replicatorLayer.instanceBlueOffset = -0.1;
replicatorLayer.instanceGreenOffset = -0.1;

// 创建小方块,replicatorLayer 将复制这个小方块
CALayer *cube = [CALayer layer];
cube.bounds = CGRectMake(0, 0, 20, 20);
cube.position = CGPointMake(150, 20);
cube.backgroundColor = [UIColor whiteColor].CGColor;
[replicatorLayer addSublayer:cube];
}

5.3 示例 2 - 一个水纹动画

下面这个示例可能需要一些动画的基础,可以参考相关 iOS 动画的章节。

一个水纹动画
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
- (void)addWaveAnimationInView:(UIView *)view
{
// 一个圆形从小到大,透明度从 1 到 0 的动画
CAShapeLayer *animLayer = [CAShapeLayer layer];
animLayer.contentsScale = [UIScreen mainScreen].scale;
animLayer.position = CGPointMake(150, 150);
animLayer.bounds = CGRectMake(0, 0, 100, 100);
animLayer.backgroundColor = [UIColor clearColor].CGColor; // 设置 layer 背景色
animLayer.fillColor = [UIColor greenColor].CGColor; // 设置填充色
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:animLayer.bounds];
animLayer.path = path.CGPath;
// 动画
animLayer.transform = CATransform3DMakeScale(0, 0, 1);
CABasicAnimation *sizeAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
sizeAnim.fromValue = @0;
sizeAnim.toValue = @1;
[animLayer addAnimation:sizeAnim forKey:nil];
CABasicAnimation *alphaAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
alphaAnim.fromValue = @1;
alphaAnim.toValue = @0;
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = @[sizeAnim, alphaAnim];
animGroup.duration = 2;
animGroup.repeatCount = HUGE;
[animLayer addAnimation:animGroup forKey:nil];

// CAReplicatorLayer
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 0, 300, 300);
[view.layer addSublayer:replicatorLayer];
replicatorLayer.instanceCount = 5;
replicatorLayer.instanceDelay = 0.4;
[replicatorLayer addSublayer:animLayer];
}

6 引用

【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]
【2】[Nick LockWood - 《iOS Core Animation: Advanced Techniques》]