iOS-高效設定圓角
一、前因
CALayer由背景色backgroundColor、內容contents、邊緣borderWidth&borderColor構成
- 設定圓角不就是設定layer的cornerRadius嗎,還談什麼高效?
因為這個屬性只會影響檢視的背景顏色和 border。所以該方法只對UIView有效,對於 UIImageView 這樣內部還有子檢視的控制元件就無能為力了。 - 所以很多情況下我們會加上layer.masksToBounds的設定。
這樣圓角效果就有了。但是,如果你勾選上 Color Offscreen-Rendered Yellow,就會發現 label 的四周出現了黃色的標記,說明這裡出現了離屏渲染。關於離屏渲染的介紹,可以參考:UIKit效能調優實戰講解。
之前有的文章說 iOS 9 做了什麼特殊優化,或者是離屏渲染的影響不大,其主要原因在於圓角不夠多。當我將一個 UIImageView 也設定成圓角,也就是螢幕上的圓角檢視達到 34 個時,fps 大幅度下降,大約只有 33 左右。基本上已經達到了影響使用者體驗的範圍。因此,一切不講依據的優化都是耍流氓,如果你的圓角檢視不多,cell 不復雜,就不要費力氣折騰了。
二、首先,來個錯誤示範:
override func drawRect(rect: CGRect) {
let maskPath = UIBezierPath(roundedRect: rect,
byRoundingCorners: .AllCorners,
cornerRadii: CGSize(width: 3, height: 3))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.CGPath
self.layer.mask = maskLayer
}
- 首先,我們應該儘量避免重寫 drawRect
方法。不恰當的使用這個方法會導致記憶體暴增。舉個例子,iPhone6 上與螢幕等大的 UIView
,即使重寫一個空的 drawRect
方法,它也至少佔用 750 * 1134 * 4 位元組 ≈ 3.4 Mb
的記憶體。在 記憶體惡鬼drawRect 及其後續中,作者詳細介紹了其中原理,據他測試,在 iPhone6 上空的、與螢幕等大的檢視重寫 drawRect
方法會消耗 5.2 Mb 記憶體。總之,能避免重寫 drawRect
方法就儘可能避免。 - 其次,這種方法本質上是用遮罩層 mask
來實現,因此同樣無可避免的會導致離屏渲染。我試著將此前 34 個檢視的圓角改用這種方法實現,結果 fps 掉到 11 左右。已經屬於卡出翔的節奏了。
三、實戰:設定圓角的正確姿勢
1.UIView設定圓角
對於 contents 無內容或者內容的背景透明(無涉及到圓角以外的區域)的layer,直接設定layer的 backgroundColor 和 cornerRadius 屬性來繪製圓角:
- UIView的contents無內容可以直接通過設定cornerRadius達到效果。
- UILable的contents也一樣,所以也可通過設定cornerRadius達到效果。不過label不能直接設定backgroundColor,因為這樣設定的是contents的backgroundColor,需要設定layer. backgroundColor。
前面提到過UIView通過cornerRadius就可以,但是如果特殊情況需要設定layer.masksToBounds,就不要通過cornerRadius方式了,會用到如下方式:
@implementation UIView (RounderCorner)
- (void)dlj_addRounderCornerWithRadius:(CGFloat)radius size:(CGSize)size
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef cxt = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(cxt, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(cxt, [UIColor redColor].CGColor);
CGContextMoveToPoint(cxt, size.width, size.height-radius);
CGContextAddArcToPoint(cxt, size.width, size.height, size.width-radius, size.height, radius);//右下角
CGContextAddArcToPoint(cxt, 0, size.height, 0, size.height-radius, radius);//左下角
CGContextAddArcToPoint(cxt, 0, 0, radius, 0, radius);//左上角
CGContextAddArcToPoint(cxt, size.width, 0, size.width, radius, radius);//右上角
CGContextClosePath(cxt);
CGContextDrawPath(cxt, kCGPathFillStroke);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[imageView setImage:image];
[self insertSubview:imageView atIndex:0];
}
這個方法返回的是 UIImage,也就是說我們利用 Core Graphics 自己畫出了一個圓角矩形。除了一些必要的程式碼外,最核心的就是 CGContextAddArcToPoint 函式。它中間的四個參數列示曲線的起點和終點座標,最後一個參數列示半徑。呼叫了四次函式後,就可以畫出圓角矩形。最後再從當前的繪圖上下文中獲取圖片並返回。
有了這個圖片後,我們建立一個 UIImageView 並插入到檢視層級的底部。
使用時,你只需要這樣寫:
[view dlj_addRounderCornerWithRadius:10 size:CGSizeMake(60, 30)];
我這裡只是單純為了實現圓角,當然大家在用的時候可以新增背景顏色、以及設定邊框的屬性。
2.ImageView新增圓角
相比於上面一種實現方法,為 UIImageView 新增圓角更為常用。它的實現思路是直接擷取圖片:
@implementation UIImage (ImageRoundedCorner)
- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
CGContextAddPath(ctx,path.CGPath);
CGContextClip(ctx);
[self drawInRect:rect];
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
圓角路徑直接用貝塞爾曲線繪製,一個意外的 bonus 是還可以選擇哪幾個角有圓角效果。這個函式的效果是將原來的 UIImage 剪裁出圓角。配合著這函式,我們可以為 UIImageView 擴充一個設定圓角的方法來更加方便的使用。
提醒
- 無論使用上面哪種方法,你都需要小心使用背景顏色。因為此時我們沒有設定 masksToBounds,因此超出圓角的部分依然會被顯示。因此,你不應該再使用背景顏色,可以在繪製圓角矩形時設定填充顏色來達到類似效果。
- 在為 UIImageView 新增圓角時,請確保 image 屬性不是 nil,否則這個設定將會無效。
四、擴充套件:其他會導致離屏渲染的解決方案
以下離屏渲染操作,按對效能影響等級從高到低進行排序:
1. shadows(陰影)
方案:在設定完layer的shadow屬性之後,設定layer.shadowPath = [UIBezierPath pathWithCGRect:view.bounds].CGPath;
2.圓角(前邊已解決過)
3.mask遮罩
方案:不用mask(哈哈)
4. allowsGroupOpacity(組不透明)
開啟CALayer的 allowsGroupOpacity 屬性後,子 layer 在視覺上的透明度的上限是其父 layer 的 opacity (對應UIView的 alpha ),並且從 iOS 7 以後預設全域性開啟了這個功能,這樣做是為了讓子檢視與其容器檢視保持同樣的透明度。
方案:關閉 allowsGroupOpacity 屬性,按產品需求自己控制layer透明度。
5. edge antialiasing(抗鋸齒)
方案:不設定 allowsEdgeAntialiasing 屬性為YES(預設為NO)
6. shouldRasterize(光柵化)
當檢視內容是靜態不變時,設定 shouldRasterize(光柵化)為YES,此方案最為實用方便。
view.layer.shouldRasterize = true;
view.layer.rasterizationScale = view.layer.contentsScale;
但當檢視內容是動態變化(如後臺下載圖片完畢後切換到主執行緒設定)時,使用此方案反而為增加系統負荷。
7.Core Graphics API(核心繪圖)
Core Graphics API(核心繪圖)的繪製操作會導致CPU的離屏渲染。
方案:放到後臺執行緒中進行。
相關文章
- 筆記-iOS設定圓角方法以及指定位置設圓角筆記iOS
- iOS圖片設定圓角iOS
- button設定邊寬和圓角
- UIView 的部分圓角的設定UIView
- WPF Button按鈕設定圓角
- css如何為圖片設定圓角CSS
- iOS高效簡易新增圓角iOS
- iOS圖片設定圓角效能問題iOS
- iOS UIView設定少於四個的圓角iOSUIView
- iOS 檢視控制元件設定圓角、陰影iOS控制元件
- Qt左上角和右下角設定圓角QT
- 直播平臺原始碼,自定義設定 View 四個角的圓角 以及邊框的設定原始碼View
- iOS 常用元件 高效切圓角方法總結iOS元件
- iOS 高效新增圓角效果實戰講解iOS
- border-radius以百分比設定圓角
- qt如何將下拉框的框設定為圓角矩形QT
- 封裝自定義圓角方向並且可設定投影的View封裝View
- Flutter 圓形/圓角頭像Flutter
- visio圓角矩形怎麼改變圓角大小
- [Android] 定製化Toast展示(位置、底色、圓角)AndroidAST
- CSS 文字框圓角CSS
- AUTOCAD——圓角命令
- iOS 繪製圓角iOS
- PHP合成圖圓角PHP
- css3圓角CSSS3
- Android 圓角、圓形 ImageView 實現AndroidView
- 利用DOTNETBAR製作圓角窗體和圓角控制元件控制元件
- canvas 繪製圓角矩形Canvas
- SVG 繪製圓角矩形SVG
- css圓角矩形邊框CSS
- UIImageView 實現圓角效果UIView
- CSS不用背景圖片實現優惠券樣式反圓角,凹圓角,反向半圓角,並且背景漸變CSS
- 根據SVG Arc求出其開始角、擺動角和橢圓圓心SVG
- cad圓角命令怎麼用 cad圓角命令無效怎麼回事
- 圓角select下拉選單
- Bootstrap按鈕圓角改成直角boot
- CSS3圓角表格效果CSSS3
- CSS3圓角詳解CSSS3