日常我們使用layer的兩個屬性,簡單的兩行代碼就能實現圓角的呈現
imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;
由于這樣處理的渲染機制是GPU在當前屏幕緩沖區外新開辟一個渲染緩沖區進行工作,也就是離屏渲染,這會給我們帶來額外的性能損耗,如果這樣的圓角操作達到一定數量,會觸發緩沖區的頻繁合并和上下文的的頻繁切換,性能的代價會宏觀地表現在用戶體驗上----掉幀。
會引發離屏渲染的操作:
The following will trigger offscreen rendering:
Any layer with a mask (layer.mask)
Any layer with layer.masksToBounds / view.clipsToBounds being true
Any layer with layer.allowsGroupOpacity set to YES and layer.opacity is less than 1.0
Any layer with a drop shadow (layer.shadow*).
Any layer with layer.shouldRasterize being true
Any layer with layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing
Text (any kind, including UILabel, CATextLayer, Core Text, etc).
Most of the drawing you do with CGContext in drawRect:. Even an empty implementation will be rendered offscreen.
因為這些效果均被認為不能直接呈現于屏幕,而需要在別的地方做額外的處理預合成。具體的檢測我們可以使用Instruments的CoreAnimation。
不用layer.cornerRadius:
/** * @brief clip the cornerRadius with image, UIImageView must be setFrame before, no off-screen-rendered */
- (void)zy_cornerRadiusWithImage:(UIImage *)image cornerRadius:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType {
CGSize size = self.bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, YES, scale);
if (nil == UIGraphicsGetCurrentContext()) {
return;
}
UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:rectCornerType cornerRadii:cornerRadii];
[cornerPath addClip];
[image drawInRect:self.bounds];
id processedImageRef = (__bridge id _Nullable)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = processedImageRef;
});
});
}
對圖片進行了切角處理后,將得到的含圓角UIImage通過-setImage傳給了UIImageView。操作沒有觸發GPU離屏渲染,過程在CPU內完成。
順便一提這里還存在一個性能問題,Color Blended Layers
,UIGraphicsBeginImageContextWithOptions(<#CGSize size#>, <#BOOL opaque#>, <#CGFloat scale#>)
的第二個參數是透明通道的開關,true則為不透明。以下兩張圖是參數傳NO or YES在模擬器中打開了Color Blended Layers Debug所看見的區別:
一些沒有被設置為opacity的圖層,因為透明通道的存在,系統需要去計算圖層堆疊后像素點的真實顏色,在Instruments的測試中也是可以高亮標顯出來,這種性能的損耗程度我還沒有專門去測試。但是在上圖可以看見如果設置為不包含透明通道,我們圖片被剪去的部分就沒有了顏色(黑漆漆一片),這里使用的解決方案就是在圖片上下文中先畫一層backgroundColor,缺點就是需要傳入:
/** * @brief clip the cornerRadius with image, draw the backgroundColor you want, UIImageView must be setFrame before, no off-screen-rendered */
- (void)zy_cornerRadiusWithImage:(UIImage *)image cornerRadius:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType backgroundColor:(UIColor *)backgroundColor {
CGSize size = self.bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, YES, scale);
if (nil == UIGraphicsGetCurrentContext()) {
return;
}
UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:rectCornerType cornerRadii:cornerRadii];
UIBezierPath *backgroundRect = [UIBezierPath bezierPathWithRect:self.bounds];
[backgroundColor setFill];
[backgroundRect fill];
[cornerPath addClip];
[image drawInRect:self.bounds];
id processedImageRef = (__bridge id _Nullable)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = processedImageRef;
});
});
}
整理來源:ZYCornerRadius