今天早上,在群里看到一個(gè)同學(xué)在問,類似下面這樣的引導(dǎo)頁,鏤空透明看到下面圖層的圈圈怎么實(shí)現(xiàn)?

其實(shí)這個(gè)東西,最簡(jiǎn)單最高效的做法,當(dāng)然是叫UI出圖。但其實(shí),用代碼我們也照樣可以實(shí)現(xiàn),也很簡(jiǎn)單,也就幾行代碼而已。
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];
上面這幾行代碼關(guān)鍵是layer的fullRule屬性,在文檔可以找到蘋果給我們提供了兩個(gè)常量值,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可以理解為順時(shí)針跟逆時(shí)針方向,順時(shí)針加1,逆時(shí)針減1,如果交叉后的結(jié)果為0,則說明某個(gè)點(diǎn)不在這個(gè)path內(nèi),也就意味著不被渲染;反之,結(jié)果為非零,就是在這個(gè)path內(nèi),就被渲染。
這樣說出來其實(shí)并不好理解,那么來一段demo,理解起來就會(huì)好點(diǎn)了。
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]; // 外路徑順時(shí)針
UIBezierPath *subPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:25 startAngle:0 endAngle:2 * M_PI clockwise:NO]; // 內(nèi)路徑逆時(shí)針
[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];
結(jié)果如下:

可以看到,外邊的path是順時(shí)針,內(nèi)部的path是逆時(shí)針,那么實(shí)際上中間的點(diǎn)的
num of crossing就為0,根據(jù)上面的描述,就會(huì)被放棄渲染,也就鏤空透明了。
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.
奇偶原則實(shí)際上非零簡(jiǎn)單,它并沒有順/逆時(shí)針之分,你可以簡(jiǎn)單的理解為路徑的重疊數(shù),number of crossings為偶數(shù),表示在path之外;為奇數(shù),表示在path之內(nèi)。同樣的,對(duì)上面的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]; // 最內(nèi)層路徑
[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];
結(jié)果如下:

可以看到,subPath內(nèi)的點(diǎn)的number of crossings為偶數(shù),沒被渲染;sub2Path內(nèi)的點(diǎn)的number of crossings為奇數(shù),被渲染;
回到文章開頭的例子,因?yàn)榫匦蝡ath是沒有順/逆時(shí)針之分,所以我們?cè)O(shè)置為kCAFillRuleEvenOdd模式,也就達(dá)到了圓形空心的效果。