iOS設置圓角的四種方法


 

小小圓角問題,正常情況下,我們不需要過多關心,但當屏幕內比較多的時候,還是有必要了解下性能問題的

 

一、設置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計算,但是應用范圍有限。

 

總結

  1. 在可以使用混合圖層遮擋的場景下,優先使用第四種方法。
  2. 即使是非iOS9以上系統,幾種辦法介於伯仲之間,甚至第一種方法使用更簡單,不過在iOS9以上由於沒有了離屏渲染更是首選。
  3. 方法二和方法三由於使用了貝塞爾曲線,都可以應對復雜的圓角。只不過前者犧牲幀率,后者需要大量計算和增加部分內存,需要實際情況各自取舍。

 

如果需要測試代碼,在我的測試demo里

 

點擊這里獲取

 

enjoy~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM