iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版

  • 什么是蒙版
  • CAShapeLayer mask
  • 为蒙版创建动画
  • 总结

什么是蒙版

我们知道,如果你想要显示一个图层的内容,需要将其加到图层的层级上

- [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图层实心的部分会被保留下来,其他的(透明的部分)则会被抛弃。如图

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第1张图片

为了更好的理解蒙版的“留下不透明部分的内容”的特性,我们来看个例子。

我们自己来用PS创建一张PNG图片作为蒙版,打开PS软件,创建一个300*300的画布,然后任意选择一种前景色(不要和背景色一样就行),然后选择画笔工具,画笔的大小稍微粗一点,比如30,然后在一旁画一个“8”(或者你喜欢的任意形状),再在旁边画另外一个形状,比如:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第2张图片

然后选择魔术橡皮擦工具,(找到橡皮擦工具,然后点右键就能找到了),把背景色的部分都变透明

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第3张图片

最后保存为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];
}

运行效果:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第4张图片

很好,接下来我们为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;

}

运行效果:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第5张图片

layer中,只有maskLayer有内容的部分被留下来了,其余部分都被挖走了。

CAShapeLayer + mask

在实际的开发中,我们经常会使用一个CAShapeLayer绘制出某个形状,然后将它作为另一个图层的蒙版来实现一些炫酷的效果。比如我们将layer剪裁成一滴水的形状。

首先我们需要创建一个“一滴水”的形状的路径,这个路径将作用于我们的maskLayer(这里的maskLayer是一个CAShapeLayer),所以路径的坐标也应该相对于maskLayer将要作用的那个图层的坐标(也就是如路径上的(0,0)点就是maskLayer将要作用的那个图层的(0,0)点)。

我们大概会画这样一个形状出来

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第6张图片

橙色部分可以通过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;
}

运行效果:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第7张图片

现在我们把上面代码中的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;

效果变成了这样:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第8张图片

这就是蒙版的“使被蒙版的图层只留下蒙版不透明部分的内容”的特性

为蒙版创建动画

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];

运行效果:

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版_第9张图片

在动画的每一帧都满足蒙版的“使被蒙版的图层只留下蒙版不透明部分的内容”的特性

所以你完全可以大开脑洞来为你的界面添加各种意想不到的效果,我们将在实战篇使用蒙版动画来实现一些其他的效果。

总结

蒙版这一章的内容比较简单,主要是让大家知道有蒙版这个东西

蒙版是作用是为一个CALayer(包括其子类)对象抠出某个形状的内容来显示,其满足“被蒙版的图层只留下蒙版不透明部分的内容”,蒙版可以是任何CALayer的子类,用的比较多的蒙版是CAShapeLayer,因为它能画出各种形状。

一旦理解了蒙版的这个特性,剩下的就只需要脑洞了。

你可能感兴趣的:(iOS动画)