我们知道,如果你想要显示一个图层的内容,需要将其加到图层的层级上
- [CALayer addSublayer:]
当然你还可以通过将一个CALayer的内容通过位图的形式渲染进CoreGraphics绘图上下文来进行一些其他的操作。
当你需要将一个CALayer的内容变成圆角的时候,你可以通过设置cornerRadius来很方便的实现,但是如果你想要一个CALayer的内容被剪裁成任意形状应该如何是好呢?
你当然可以通过使用一张拥有alpha属性的PNG图片来直接绘制,但是这样做的话就没有动态特性了,比如剪裁形状和要剪裁的图片本身都是用户指定的,这样的话就必须用编程来动态实现了。
如果你使用过Photoshop,这个问题你肯定知道可以创建一个图层蒙版来实现。而在CoreAnimation中,框架同样为我们提供了这样的功能,CALayer拥有一个属性叫做mask,作为这个CALayer对象的蒙版,mask本身也是一个CALayer,比如:
CALayer * layer = [CALayer layer];
CALayer * maskLayer = [CALayer layer];
layer.mask = maskLayer;
这样的话,maskLayer就成为了layer的蒙版,maskLayer类似于一个子图层,相对于父图层(即拥有该属性的图层,在这里就是layer)布局,但是它却不是一个普通的子图层。maskLayer并不会直接绘制在父图层之上,它只是定义了父图层的“可视部分”。
想象maskLayer是一张纸,盖在了layer上,那么layer能显示出来的内容,就是maskLayer“不是透明的部分”的内容。mask属性就像是一个饼干切割机,mask图层实心的部分会被保留下来,其他的(透明的部分)则会被抛弃。如图
为了更好的理解蒙版的“留下不透明部分的内容”的特性,我们来看个例子。
我们自己来用PS创建一张PNG图片作为蒙版,打开PS软件,创建一个300*300的画布,然后任意选择一种前景色(不要和背景色一样就行),然后选择画笔工具,画笔的大小稍微粗一点,比如30,然后在一旁画一个“8”(或者你喜欢的任意形状),再在旁边画另外一个形状,比如:
然后选择魔术橡皮擦工具,(找到橡皮擦工具,然后点右键就能找到了),把背景色的部分都变透明
最后保存为PNG(注意一定要是PNG格式)。我们将这张图片拖进我们的工程里面,它将作为我们的蒙版,这里我命名为mask.png。然后随便找一张你喜欢的图片作为我们要绘制的内容,也将是被蒙版的图片,我命名为content.png。
接下来我们使用代码来进行显示:
- (void)viewDidLoad {
[super viewDidLoad];
CALayer * layer = [CALayer layer];
layer.frame = CGRectMake(80, 80, 300, 300);
// 直接设置layer的contents属性,它可以是一张图片的内容,但是我们的layer同样不认识UIKit下面的UIImage,它只接收CGImageRef,所以使用桥接来进行强转
layer.contents = (__bridge id)[UIImage imageNamed:@"content.png"].CGImage;
[self.view.layer addSublayer:layer];
}
运行效果:
很好,接下来我们为layer加上蒙版:
- (void)viewDidLoad {
[super viewDidLoad];
CALayer * layer = [CALayer layer];
layer.frame = CGRectMake(80, 80, 300, 300);
// 直接设置layer的contents属性,它可以是一张图片的内容,但是我们的layer同样不认识UIKit下面的UIImage,它只接收CGImageRef,所以使用桥接来进行强转
layer.contents = (__bridge id)[UIImage imageNamed:@"content.png"].CGImage;
[self.view.layer addSublayer:layer];
CALayer * maskLayer = [CALayer layer];
// 蒙版的坐标是基于它所影响的那个图层的坐标系
maskLayer.frame = CGRectMake(0, 0, 300, 300);
maskLayer.contents = (__bridge id)[UIImage imageNamed:@"mask.png"].CGImage;
// 将maskLayer作为layer的蒙版
layer.mask = maskLayer;
}
运行效果:
layer中,只有maskLayer有内容的部分被留下来了,其余部分都被挖走了。
在实际的开发中,我们经常会使用一个CAShapeLayer绘制出某个形状,然后将它作为另一个图层的蒙版来实现一些炫酷的效果。比如我们将layer剪裁成一滴水的形状。
首先我们需要创建一个“一滴水”的形状的路径,这个路径将作用于我们的maskLayer(这里的maskLayer是一个CAShapeLayer),所以路径的坐标也应该相对于maskLayer将要作用的那个图层的坐标(也就是如路径上的(0,0)点就是maskLayer将要作用的那个图层的(0,0)点)。
我们大概会画这样一个形状出来
橙色部分可以通过addLineToPoint来绘制,蓝色部分则需要通过addArc来绘制圆弧或者addQuadCurve来绘制一个二阶贝塞尔曲线,我们这里使用二阶贝塞尔曲线更加灵活一些。
- (void)viewDidLoad {
[super viewDidLoad];
CALayer * layer = [CALayer layer];
layer.frame = CGRectMake(80, 80, 300, 300);
// 直接设置layer的contents属性,它可以是一张图片的内容,但是我们的layer同样不认识UIKit下面的UIImage,它只接收CGImageRef,所以使用桥接来进行强转
layer.contents = (__bridge id)[UIImage imageNamed:@"content.png"].CGImage;
[self.view.layer addSublayer:layer];
UIBezierPath * bezierPath = [UIBezierPath bezierPath];
// 起始点在(图片宽的一半,0)的位置
[bezierPath moveToPoint:CGPointMake(150, 0)];
// 大概估算一下x和y的值
[bezierPath addLineToPoint:CGPointMake(40, 150)];
// 向右拉一条二阶贝塞尔曲线,控制点在中部偏下
[bezierPath addQuadCurveToPoint:CGPointMake(260, 150) controlPoint:CGPointMake(150, 300)];
// 闭合曲线,这样就会从当前点(150,300)到起始点(150,0)连线来进行闭合
[bezierPath closePath];
// 构造蒙版图层
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.path = bezierPath.CGPath;
// 因为maskLayer的填充颜色默认是存在的,所以可以直接作为蒙版
layer.mask = maskLayer;
}
运行效果:
现在我们把上面代码中的maskLayer的设置改一下:
// 构造蒙版图层
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.path = bezierPath.CGPath;
// 因为maskLayer的填充颜色默认是存在的,所以可以直接作为蒙版
layer.mask = maskLayer;
// 填充颜色为透明,填充内容将不再作为蒙版内容
maskLayer.fillColor = [UIColor clearColor].CGColor;
// 任意颜色,只要不透明就行,描线颜色就将作为蒙版内容
maskLayer.strokeColor = [UIColor redColor].CGColor;
maskLayer.lineWidth = 30;
效果变成了这样:
这就是蒙版的“使被蒙版的图层只留下蒙版不透明部分的内容”的特性
CALayer的蒙版可以是任何CALayer的子类,既然是CALayer的子类,当然可以拥有动画效果了。
我们为上面的maskLayer添加一个strokeEnd的动画来试一试效果。接着上面的代码添加如下动画代码:
CABasicAnimation * animation = [CABasicAnimation animation];
animation.keyPath = @"strokeEnd";
animation.duration = 3;
animation.fromValue = @0;
// 由于maskLayer默认的strokeEnd就是1,所以这里不再需要重新设置modelLayer的属性
[maskLayer addAnimation:animation forKey:nil];
运行效果:
在动画的每一帧都满足蒙版的“使被蒙版的图层只留下蒙版不透明部分的内容”的特性
所以你完全可以大开脑洞来为你的界面添加各种意想不到的效果,我们将在实战篇使用蒙版动画来实现一些其他的效果。
蒙版这一章的内容比较简单,主要是让大家知道有蒙版这个东西
蒙版是作用是为一个CALayer(包括其子类)对象抠出某个形状的内容来显示,其满足“被蒙版的图层只留下蒙版不透明部分的内容”,蒙版可以是任何CALayer的子类,用的比较多的蒙版是CAShapeLayer,因为它能画出各种形状。
一旦理解了蒙版的这个特性,剩下的就只需要脑洞了。