小小圓角問題,正常情況下,我們不需要過多關心,但當屏幕內比較多的時候,還是有必要了解下性能問題的
一、設置CALayer的cornerRadius
這是最常用的,也是最簡單的。
cornerRadius屬性影響layer顯示的background顏色和前景框border,但對layer的contents不起作用。
所以一個imgView(類型為UIImageView)的image不為空,設置imgView.layer的cornerRadius,是看不出顯示圓角效果的,因為image是imgView.layer的contents部分;只有將layer的masksToBounds屬性也設置為YES,才能繪制出圓角效果。
但是cornerRadius>0,masksToBounds=YES,會觸發GPU的離屏渲染,當一個屏幕上有多處觸發離屏渲染,會影響一定性能。
(如果查看性能概況,可參考查看這篇:通過勾選Instruments->Core Animation->Color Offscreen-Rendered Yellow,可以看到屏幕上觸發離屏渲染的會被渲染成黃色。)
離屏渲染會降低fps,蘋果也意識到會產生性能問題,所以iOS9以后的系統里能不產生離屏渲染的地方也就不用離屏渲染了。
比如對UIImageView里png圖片設置圓角不會觸發離屏渲染。
1、對contents為空的視圖設置圓角 imageView.layer.backgroundColor = [[UIColor redColor] CGColor]; imageView.layer.cornerRadius = 25; 2、對contents不為空的視圖設置圓角 imageView.image = [UIImage imageNamed:@"img"]; imageView.layer.cornerRadius = 5; imageView.layer.masksToBounds = YES;//必須加
這里延伸一下,如果對一個label或button設置圓角,也可以使用layer.backgroundColor和layer.cornerRadius設置,而不需要layer.maskstoBounds。
這樣不會觸發離屏渲染,所以ios9之后,可以直接這樣做。
二、設置CALayer的mask
通過設置view.layer的mask屬性,可以將另一個layer蓋在view上,也可以設置圓角,但是mask同樣會觸發離屏渲染。
有兩種方式來生成遮罩:
一是通過圖片生成,圖片的透明度影響着view繪制的透明度,圖片遮罩透明度為1的部分view被繪制成的透明度為0,相反圖片遮罩透明度為0的部分view被繪制成的透明度為1。
二是通過貝塞爾曲線生成,view中曲線描述的形狀部分會被繪制出來。
// 通過圖片生成遮罩, UIImage *maskImage = [UIImage imageNamed:@"someimg"]; CALayer *mask = [CALayer new]; mask.frame = CGRectMake(0, 0, maskImage.size.width, maskImage.size.height); mask.contents = (__bridge id _Nullable)(maskImage.CGImage); view.layer.mask = mask; //通過貝塞爾曲線生成 CAShapeLayer *mask = [CAShapeLayer new]; mask.path = [UIBezierPath bezierPathWithOvalInRect:view.bounds].CGPath; view.layer.mask = mask;
第一種找遮罩圖麻煩點,第二種足以。
三、通過Core Graphics重新繪制帶圓角的視圖
通過CPU重新繪制一份帶圓角的視圖來實現圓角效果,會大大增加CPU的負擔,而且相當於多了一份視圖拷貝會增加內存開銷。但是就顯示性能而言,由於沒有觸發離屏渲染,所以能保持較高幀率。下例是繪制一個圓形圖片,繪制其它UIView並無本質區別。重新繪制的過程可以交由后台線程來處理。
@implementation UIImage (CircleImage) - (UIImage *)drawCircleImage { CGFloat side = MIN(self.size.width, self.size.height); UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale); CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath); CGContextClip(UIGraphicsGetCurrentContext()); CGFloat marginX = -(self.size.width - side) / 2.f; CGFloat marginY = -(self.size.height - side) / 2.f; [self drawInRect:CGRectMake(marginX, marginY, self.size.width, self.size.height)]; CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke); UIImage *output = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return output; } @end //在需要圓角時調用如下 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *img = [[UIImage imageNamed:@"image.png"] drawCircleImage]; dispatch_async(dispatch_get_main_queue(), ^{ view.image = img; }); });
其實感覺這種方式有我不是很推薦,我測試過幾次,cpu會大大增加,內存也會有一點增加,而且當屏幕內大概有200個圓角時,偶發一次會崩潰。
四、通過混合圖層
此方法就是在要添加圓角的視圖上再疊加一個部分透明的視圖,只對圓角部分進行遮擋。其實就是中間圓形部分透明,不遮擋底部的控件,不過同時也需要遮擋顏色和view背景色一致才行。
此方法雖然是最優解,沒有離屏渲染,沒有額外的CPU計算,但是應用范圍有限。
總結
- 在可以使用混合圖層遮擋的場景下,優先使用第四種方法。
- 即使是非iOS9以上系統,幾種辦法介於伯仲之間,甚至第一種方法使用更簡單,不過在iOS9以上由於沒有了離屏渲染更是首選。
- 方法二和方法三由於使用了貝塞爾曲線,都可以應對復雜的圓角。只不過前者犧牲幀率,后者需要大量計算和增加部分內存,需要實際情況各自取舍。
如果需要測試代碼,在我的測試demo里
enjoy~