今天早上,在群里看到一個同學(xué)在問,類似下面這樣的引導(dǎo)頁,鏤空透明看到下面圖層的圈圈怎么實現(xiàn)?
其實這個東西,最簡單最高效的做法,當(dāng)然是叫UI出圖。但其實,用代碼我們也照樣可以實現(xià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
屬性,在文檔可以找到蘋果給我們提供了兩個常量值,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,如果交叉后的結(jié)果為0,則說明某個點不在這個path內(nèi),也就意味著不被渲染;反之,結(jié)果為非零,就是在這個path內(nèi),就被渲染。
這樣說出來其實并不好理解,那么來一段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]; // 內(nèi)路徑逆時針
[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是順時針,內(nèi)部的path是逆時針,那么實際上中間的點的
num of crossing
就為0,根據(jù)上面的描述,就會被放棄渲染,也就鏤空透明了。
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ù),number of crossings
為偶數(shù),表示在path之外;為奇數(shù),表示在path之內(nèi)。同樣的,對上面的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)的點的number of crossings
為偶數(shù),沒被渲染;sub2Path內(nèi)的點的number of crossings
為奇數(shù),被渲染;
回到文章開頭的例子,因為矩形path是沒有順/逆時針之分,所以我們設(shè)置為kCAFillRuleEvenOdd
模式,也就達(dá)到了圓形空心的效果。