今天早上,在群里看到一个同学在问,类似下面这样的引导页,镂空透明看到下面图层的圈圈怎么实现?
其实这个东西,最简单最高效的做法,当然是叫UI出图。但其实,用代码我们也照样可以实现,也很简单,也就几行代码而已。
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.view.bounds);
CGPathRef subPath = CGPathCreateWithEllipseInRect(CGRectMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height - 50, 50, 50), NULL);
CGPathAddPath(path, NULL, subPath);
CGPathCloseSubpath(path);
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = path;
maskLayer.fillColor = [[UIColor blackColor] colorWithAlphaComponent:0.55].CGColor;
maskLayer.fillRule = kCAFillRuleEvenOdd;
[self.view.layer addSublayer:maskLayer];
上面这几行代码关键是layer的fullRule
属性,在文档可以找到苹果给我们提供了两个常量值,kCAFillRuleNonZero
和 kCAFillRuleEvenOdd
,引用官方文档的解释
kCAFillRuleNonZero
kCAFillRuleNonZero // 非零
Specifies the non-zero winding rule. Count each left-to-right path as +1 and each right-to-left path as -1. If the sum of all crossings is 0, the point is outside the path. If the sum is nonzero, the point is inside the path and the region containing it is filled.
这里的left-to-right
跟right-to-left
可以理解为顺时针跟逆时针方向,顺时针加1,逆时针减1,如果交叉后的结果为0,则说明某个点不在这个path内,也就意味着不被渲染;反之,结果为非零,就是在这个path内,就被渲染。
这样说出来其实并不好理解,那么来一段demo,理解起来就会好点了。
CGPoint arcCenter = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:50 startAngle:0 endAngle:2 * M_PI clockwise:YES]; // 外路径顺时针
UIBezierPath *subPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:25 startAngle:0 endAngle:2 * M_PI clockwise:NO]; // 内路径逆时针
[path appendPath:subPath];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = path.CGPath;
maskLayer.fillColor = [UIColor yellowColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.fillRule = kCAFillRuleNonZero; // 非零模式
[self.view.layer addSublayer:maskLayer];
结果如下:
可以看到,外边的path是顺时针,内部的path是逆时针,那么实际上中间的点的
num of crossing
就为0,根据上面的描述,就会被放弃渲染,也就镂空透明了。
kCAFillRuleEvenOdd
** kCAFillRuleEvenOdd** // 奇偶
Specifies the even-odd winding rule. Count the total number of path crossings. If the number of crossings is even, the point is outside the path. If the number of crossings is odd, the point is inside the path and the region containing it should be filled.
奇偶原则实际上非零简单,它并没有顺/逆时针之分,你可以简单的理解为路径的重叠数,number of crossings
为偶数,表示在path之外;为奇数,表示在path之内。同样的,对上面的demo稍作修改
CGPoint arcCenter = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:50 startAngle:0 endAngle:2 * M_PI clockwise:YES]; // 最外路径
UIBezierPath *subPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:25 startAngle:0 endAngle:2 * M_PI clockwise:NO]; // 第二层路径
UIBezierPath *sub2Path = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:10 startAngle:0 endAngle:2 * M_PI clockwise:YES]; // 最内层路径
[path appendPath:subPath];
[path appendPath:sub2Path];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = path.CGPath;
maskLayer.fillColor = [UIColor yellowColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.fillRule = kCAFillRuleEvenOdd;
[self.view.layer addSublayer:maskLayer];
结果如下:
可以看到,subPath内的点的number of crossings
为偶数,没被渲染;sub2Path内的点的number of crossings
为奇数,被渲染;
回到文章开头的例子,因为矩形path是没有顺/逆时针之分,所以我们设置为kCAFillRuleEvenOdd
模式,也就达到了圆形空心的效果。