日常我們使用layer的兩個(gè)屬性,簡(jiǎn)單的兩行代碼就能實(shí)現(xiàn)圓角的呈現(xiàn)
imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;
由于這樣處理的渲染機(jī)制是GPU在當(dāng)前屏幕緩沖區(qū)外新開辟一個(gè)渲染緩沖區(qū)進(jìn)行工作,也就是離屏渲染,這會(huì)給我們帶來額外的性能損耗,如果這樣的圓角操作達(dá)到一定數(shù)量,會(huì)觸發(fā)緩沖區(qū)的頻繁合并和上下文的的頻繁切換,性能的代價(jià)會(huì)宏觀地表現(xiàn)在用戶體驗(yàn)上----掉幀。
會(huì)引發(fā)離屏渲染的操作:
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.
因?yàn)檫@些效果均被認(rèn)為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成。具體的檢測(cè)我們可以使用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;
});
});
}
對(duì)圖片進(jìn)行了切角處理后,將得到的含圓角UIImage通過-setImage傳給了UIImageView。操作沒有觸發(fā)GPU離屏渲染,過程在CPU內(nèi)完成。
順便一提這里還存在一個(gè)性能問題,Color Blended Layers
,UIGraphicsBeginImageContextWithOptions(<#CGSize size#>, <#BOOL opaque#>, <#CGFloat scale#>)
的第二個(gè)參數(shù)是透明通道的開關(guān),true則為不透明。以下兩張圖是參數(shù)傳NO or YES在模擬器中打開了Color Blended Layers Debug所看見的區(qū)別:
一些沒有被設(shè)置為opacity的圖層,因?yàn)橥该魍ǖ赖拇嬖冢到y(tǒng)需要去計(jì)算圖層堆疊后像素點(diǎn)的真實(shí)顏色,在Instruments的測(cè)試中也是可以高亮標(biāo)顯出來,這種性能的損耗程度我還沒有專門去測(cè)試。但是在上圖可以看見如果設(shè)置為不包含透明通道,我們圖片被剪去的部分就沒有了顏色(黑漆漆一片),這里使用的解決方案就是在圖片上下文中先畫一層backgroundColor,缺點(diǎn)就是需要傳入:
/** * @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