寫在前面
iOS設(shè)置圓角的性能探究已經(jīng)是一個老生常談的問題了朴译,眾所周知耐齐,如果直接使用layer
的cornerRadius
+ masksToBounds
雖然可以很方便的完成圓角設(shè)置埃撵,但會引起離屏渲染堕虹,導(dǎo)致性能問題否纬,在列表視圖中過多的圓角設(shè)置就會導(dǎo)致滑動卡頓仍秤,現(xiàn)在主流的方案就是在獲取圖片的一刻開啟異步線程對圖片進(jìn)行相應(yīng)的圓角處理,把圖片處理成想要的圖片在返回主線程進(jìn)行顯示彼宠,想要便捷的達(dá)成此目的推薦YY大神的YYWebImage
鳄虱,其在獲取圖片的時候的時候提供了一個transform
的block
,在此block
中你可以完成圖片的處理工作凭峡,但是在實(shí)際的使用中拙已,我覺得第二種方案還是有些不方便的地方,具體如下:
1摧冀、對于網(wǎng)絡(luò)圖片倍踪,大多數(shù)要提前設(shè)置展位圖,如果美工沒有提供圓角占位圖片索昂,你需要相應(yīng)的對占位圖片進(jìn)行圓角處理建车;
2、對于混合視圖需要圓角的楼镐,比如下圖癞志,圖片上有一個Label,label也需要圓角化框产,你也得對label進(jìn)行單獨(dú)的處理(這里我有個小tip:如果必須使用這種方式凄杯,我的做法是生成一張左下角和右下角圓角化的黑色背景圖片错洁,然后使用colorWithPatternImage
將圖片設(shè)置label的背景色,這樣你不需要為這個黑色的圓角地圖另外創(chuàng)建一個視圖)
3戒突、如果多處需要重復(fù)使用同一個圖片地址屯碴,使用YYWebImage
時,其會將tranform后的圖片緩存起來膊存,所以就會出現(xiàn)导而,如果你在一個地方圓角化了該圖片,在另一個地方使用時依然會是圓角化的圖片隔崎,這顯然在有時候是不滿足需求的今艺,不過你可以使用不同的YYWebImageManager
來管理相同圖片地址而需要不同transform的圖片;
綜上所述爵卒,雖然可以通過一些方式解決上述問題虚缎,如果有一個性能優(yōu)秀且能避免上訴問題產(chǎn)生的圓角化方案就更好了。
我的方案
要避免上述問題钓株,我們就不能從修改圖片入手了实牡,還是需要從視圖層次入手,我采取的方案其實(shí)也相當(dāng)簡單轴合,如果某個視圖需要圓角化创坞,我只需要在該視圖上添加一個子layer到最上層,用于遮蓋該視圖及其子視圖受葛,設(shè)置layer的圖片為剛好能夠遮蓋成所需圓角樣子并且圖片顏色剛好是該視圖父視圖的背景顏色就達(dá)到達(dá)到想要的效果的题涨,由于該遮罩layer在最上層,所以對于上面所提到的第二個缺點(diǎn)中的Label总滩,也順帶著遮罩了携栋,所以無需再次處理,當(dāng)然由于我們是在視圖層次而非圖片層次處理的圓角咳秉,上面的第一個和第三個缺點(diǎn)也不存在了,這樣其實(shí)很簡單的解決的上訴三個缺點(diǎn)鸯隅,下面來看看相關(guān)的代碼:
1澜建、首先是繪制遮蓋layer的圖層圖片,當(dāng)然我們可以讓美工切圖給我們蝌以,但是如果對于每個尺寸的視圖都去切圖的話炕舵,工作量就相應(yīng)增大了,其實(shí)我們值需要繪制一張如下的圖片跟畅,如果是空白咽筋,請點(diǎn)擊圖片查看
先來看看繪制代碼
/**我創(chuàng)建了一個分類用于創(chuàng)建相應(yīng)的遮罩圖片*/
@implementation UIImage (XWAddForRoundedCorner)
/**提供一個在一個指定的size中繪制圖片的便捷方法*/
+ (UIImage *)xw_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
if (!drawBlock) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) return nil;
drawBlock(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
/**繪制方法的具體邏輯,遮罩圖片的邏輯是繪制一個矩形徊件,然后在繪制一個相應(yīng)的圓角矩形奸攻,然后填充矩形和圓角矩形的中間部分為父視圖的背景色*/
+ (UIImage *)xw_maskRoundCornerRadiusImageWithColor:(UIColor *)color cornerRadii:(CGSize)cornerRadii size:(CGSize)size corners:(UIRectCorner)corners borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth{
return [UIImage xw_imageWithSize:size drawBlock:^(CGContextRef _Nonnull context) {
CGContextSetLineWidth(context, 0);
[color set];
CGRect rect = CGRectMake(0, 0, size.width, size.height);
//繪制一個矩形蒜危,這里發(fā)-0.3是為了防止邊緣的鋸齒,
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectInset(rect, -0.3, -0.3)];
//繪制圓角矩形睹耐,這里的0.3是為了防止內(nèi)邊框的鋸齒
UIBezierPath *roundPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, 0.3, 0.3) byRoundingCorners:corners cornerRadii:cornerRadii];
[rectPath appendPath:roundPath];
CGContextAddPath(context, rectPath.CGPath);
//注意要用EOFill方式進(jìn)行填充而非Fill方式
CGContextEOFillPath(context);
//如下是繪制邊框辐赞,原理依舊是繪制一個外邊框然后根據(jù)邊框?qū)挾壤L制一個內(nèi)邊框同樣采取EOFill的方式進(jìn)行填充即可
if (!borderColor || !borderWidth) return;
[borderColor set];
UIBezierPath *borderOutterPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:cornerRadii];
UIBezierPath *borderInnerPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:cornerRadii];
[borderOutterPath appendPath:borderInnerPath];
CGContextAddPath(context, borderOutterPath.CGPath);
CGContextEOFillPath(context);
}];
}
@end
上述的繪制方法充分利用了系統(tǒng)繪制圓角的方法bezierPathWithRoundedRect
+ EOFill
,其實(shí)最開始我是考慮自己繪制四分之一的圓弧來構(gòu)建圓角的,后來發(fā)現(xiàn)不但代碼多了不少硝训,而且繪制出來的圓角始終沒有系統(tǒng)這個繪制方法的圓潤响委,我打印的系統(tǒng)的圓角路徑,發(fā)現(xiàn)其應(yīng)該不是四分之一圓弧窖梁,發(fā)現(xiàn)他的控制點(diǎn)取值有些奇怪赘风,我也不太清楚是如何取的,但是效果的確要好一點(diǎn)纵刘,總之采取如上的EOFill
方式取巧邀窃,繪制的圓角圖片可以達(dá)到和系統(tǒng)的cornerRadius
完全相同的效果!
2彰导、對于繪制的圖片蛔翅,我們應(yīng)該進(jìn)行保存,當(dāng)遇到顏色位谋,圓角以及邊框等屬性完全相同的繪制請求時候山析,我們可以及時復(fù)用,避免多次創(chuàng)建
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)的話掏父,主要是避免了去解決上面說道提到的幾個問題笋轨,如果你有遇到上面3個問題的困擾,我覺得這是相當(dāng)不錯的方案
再羅列缺點(diǎn):
1赊淑、由于創(chuàng)建圖片需要一個背景色爵政,該背景色源于需要遮蓋視圖的父視圖的顏色,如果該父視圖的顏色不是純色或者存在透明的話陶缺,此時該方式就不適用了钾挟,此時還是應(yīng)該采取處理圖片的方式來解決;
2饱岸、如果父視圖的顏色會變化掺出,比如點(diǎn)擊cell的時候,此刻你需要同時更新遮罩圖片為相應(yīng)顏色的圖片苫费,所以需要多寫一些代碼汤锨。
封裝
對于此方案,我封裝了一個簡單的UIView
的分類UIView+XWAddForRoundedCorner
來達(dá)到目的百框,github地址是XWCornerRadius 闲礼,此分類分成簡單只有3個API ,且代碼只有200行,沒有其它依賴柬泽,具體API如下:
/**
設(shè)置一個四角圓角
@param radius 圓角半徑
@param color 圓角背景色
*/
- (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color;
/**
設(shè)置一個普通圓角
@param radius 圓角半徑
@param color 圓角背景色
@param corners 圓角位置
*/
- (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color corners:(UIRectCorner)corners;
/**
設(shè)置一個帶邊框的圓角
@param cornerRadii 圓角半徑cornerRadii
@param color 圓角背景色
@param corners 圓角位置
@param borderColor 邊框顏色
@param borderWidth 邊框線寬
*/
- (void)xw_roundedCornerWithCornerRadii:(CGSize)cornerRadii cornerColor:(UIColor *)color corners:(UIRectCorner)corners borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth;
你只需要調(diào)用相應(yīng)的API就能完成一個圓角 + 邊框的遮罩效果慎菲,相同遮罩圖片的復(fù)用我也做了相關(guān)處理了,具體使用如下:
[headerView xw_roundedCornerWithCornerRadii:XWSizeMake(40, 40) cornerColor:[UIColor whiteColor] corners:UIRectCornerAllCorners borderColor:[UIColor redColor] borderWidth:widthRatio(2)];
工程的demo中包含一個如下的列表(該截圖來自很老的ipod)聂抢,演示了具體的使用方法钧嘶,請自行查看:
寫在最后
對于此方法我已經(jīng)做了簡單的測試,性能還是相當(dāng)不錯的琳疏,雖然有圖層混合有决,但是相對于離屏渲染,都是小問題空盼,如上圖在我的老ipod的上也相當(dāng)流暢书幕,大家可以自行嘗試,更多的關(guān)于這幾種方案的性能比較揽趾,網(wǎng)上也已經(jīng)很多了台汇,大家可以自行查找,當(dāng)然你也可以使用YYWebImage
來處理圖片的尺寸來進(jìn)一步優(yōu)化圖片的展示篱瞎, 如果在某些非常復(fù)雜的場景想要進(jìn)一步提高流暢度帮哈,YYKit的源碼以及 YY大神關(guān)于性能的相關(guān)文章絕對值得反復(fù)閱讀舟肉,不過對于優(yōu)化的態(tài)度蜂厅,我覺得還是先考慮需求的實(shí)現(xiàn)粮揉,再來考慮性能優(yōu)化最好,所謂過早的性能優(yōu)化都是魔鬼嘛~~~對于該圓角思路如果有疑問歡迎提出澄者,如果覺得有幫助笆呆,感謝star,再復(fù)習(xí)一遍github地址:XWCornerRadius