?本文為搬運(yùn)貼,好帖子還是怕blog失效格了,多一份分享 資料保存更長久。
?iOS 高效添加圓角效果實(shí)戰(zhàn)講解
圓角(RounderCorner)是一種很常見的視圖效果徽鼎,相比于直角,它更加柔和優(yōu)美弹惦,易于接受否淤。但很多人并不清楚如何設(shè)置圓角的正確方式和原理。設(shè)置圓角會(huì)帶來一定的性能損耗棠隐,如何提高性能是另一個(gè)需要重點(diǎn)討論的話題石抡。我查閱了一些現(xiàn)有的資料,收獲良多的同時(shí)也發(fā)現(xiàn)了一些誤導(dǎo)人錯(cuò)誤助泽。本文總結(jié)整理了一些知識(shí)點(diǎn)啰扛,概括如下:
設(shè)置圓角的正確姿勢及其原理
設(shè)置圓角的性能損耗
其他設(shè)置圓角的方法,以及最優(yōu)選擇
我為本文制作了一個(gè) demo嗡贺,讀者可以在我的 github 上 clone 下來:CornerRadius隐解,如果覺得有幫助還望給個(gè)star以示支持。項(xiàng)目由 Swift 實(shí)現(xiàn)诫睬,但請(qǐng)務(wù)必相信我即使你只會(huì) Objective-C煞茫,也可以看懂它。因?yàn)槠渲械年P(guān)鍵知識(shí)與 Swift 無關(guān)。
正確姿勢:
首先续徽,我想要聲明的一點(diǎn)是:
設(shè)置圓角很簡單蚓曼,它不會(huì)帶來任何性能損耗
因?yàn)檫@件事本來就很簡單,它只需要一行代碼:
view.layer.cornerRadius =5
先別急著關(guān)掉網(wǎng)頁钦扭,也別急著回復(fù)纫版,我們讓事實(shí)說話。打開 Instuments客情,選擇?Core Animation調(diào)試其弊,你會(huì)發(fā)現(xiàn)既沒有 Off-Screen Render,也沒有降低幀數(shù)裹匙。關(guān)于使用 Instuments 分析應(yīng)用瑞凑,你可以參考我的這篇文章:UIKit性能調(diào)優(yōu)實(shí)戰(zhàn)講解。從截圖中可以看到第三個(gè)棕色視圖確確實(shí)實(shí)設(shè)置了圓角:
不過查看一下代碼可以發(fā)現(xiàn)概页,有一個(gè)?UILabel?也設(shè)置了圓角籽御,但是沒有表現(xiàn)出任何變化。關(guān)于這一點(diǎn)惰匙,你可以查看?cornerRadius?屬性的注釋:
By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.
也就是說在默認(rèn)情況下技掏,這個(gè)屬性只會(huì)影響視圖的背景顏色和 border。對(duì)于?UILabel?這樣內(nèi)部還有子視圖的控件就無能為力了项鬼。所以很多情況下我們會(huì)看到這樣的代碼:
label.layer.cornerRadius = 5
label.layer.masksToBounds =true
我們把第二行代碼添加到?CustomTableViewCell?的構(gòu)造方法中哑梳,再次運(yùn)行 Instument,就可以看到圓角效果了绘盟。
性能損耗
如果你勾選上?Color Offscreen-Rendered Yellow鸠真,就會(huì)發(fā)現(xiàn) label 的四周出現(xiàn)了黃色的標(biāo)記,說明這里出現(xiàn)了離屏渲染龄毡。關(guān)于離屏渲染的介紹吠卷,同樣可以參考:UIKit性能調(diào)優(yōu)實(shí)戰(zhàn)講解,就不在本文贅述了沦零。
需要強(qiáng)調(diào)的一點(diǎn)是祭隔,離屏渲染并非由設(shè)置圓角導(dǎo)致的!通過控制變量的方法很容易得出這個(gè)結(jié)論路操,因?yàn)?UIView 只是設(shè)置了?cornerRadius疾渴,但它沒有出現(xiàn)離屏渲染。某些比較權(quán)威的文章屯仗,比如?Stackoverflow?和?CodeReview?都提到設(shè)置?cornerRadius?會(huì)導(dǎo)致離屏渲染從而影響性能搞坝,我想這實(shí)在是冤枉了可愛的?cornerRadius?變量,也誤導(dǎo)了別人魁袜。
雖然設(shè)置?masksToBounds?會(huì)導(dǎo)致離屏渲染瞄沙,從而影響性能己沛,但是這個(gè)影響到底會(huì)有多大?在我的 iPhone6 上距境,即使出現(xiàn)了 17 個(gè)帶有圓角的視圖申尼,滑動(dòng)時(shí)的幀數(shù)依然在 58 - 59 fps 左右波動(dòng)。
然而垫桂,這并非說明 iOS 9 做了什么特殊優(yōu)化师幕,或者是離屏渲染的影響不大,其主要原因在于圓角不夠多诬滩。當(dāng)我將一個(gè)?UIImageView?也設(shè)置成圓角霹粥,也就是屏幕上的圓角視圖達(dá)到 34 個(gè)時(shí),fps 大幅度下降疼鸟,大約只有 33 左右后控。基本上已經(jīng)達(dá)到了影響用戶體驗(yàn)的范圍空镜。因此浩淘,一切不講依據(jù)的優(yōu)化都是耍流氓,如果你的圓角視圖不多吴攒,cell 不復(fù)雜张抄,就不要費(fèi)力氣折騰了。
高效地設(shè)置圓角
假設(shè)現(xiàn)在圓角視圖非常多(比如在 UICollectionView 中)洼怔,那么如何為視圖高效的添加圓角呢署惯?網(wǎng)上的教程大多沒有說全,因?yàn)檫@個(gè)事要分兩種情況考慮镣隶。為普通的?UIView?設(shè)置圓角极谊,和為?UIImageView?設(shè)置圓角的原理截然不同。
有一種做法是這樣的安岂,這種寫法試圖實(shí)現(xiàn)?cornerRadius = 3?的效果:
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
}
不過這是一種錯(cuò)的離譜的寫法怀酷!
首先,我們應(yīng)該盡量避免重寫?drawRect?方法嗜闻。不恰當(dāng)?shù)氖褂眠@個(gè)方法會(huì)導(dǎo)致內(nèi)存暴增。舉個(gè)例子桅锄,iPhone6 上與屏幕等大的?UIView琉雳,即使重寫一個(gè)空的?drawRect?方法,它也至少占用?750 * 1134 * 4 字節(jié) ≈ 3.4 Mb?的內(nèi)存友瘤。在?內(nèi)存惡鬼drawRect?及其后續(xù)中翠肘,作者詳細(xì)介紹了其中原理,據(jù)他測試辫秧,在 iPhone6 上空的束倍、與屏幕等大的視圖重寫?drawRect?方法會(huì)消耗 5.2 Mb 內(nèi)存。總之绪妹,能避免重寫?drawRect?方法就盡可能避免甥桂。
其次,這種方法本質(zhì)上是用遮罩層?mask?來實(shí)現(xiàn)邮旷,因此同樣無可避免的會(huì)導(dǎo)致離屏渲染黄选。我試著將此前 34 個(gè)視圖的圓角改用這種方法實(shí)現(xiàn),結(jié)果 fps 掉到 11 左右婶肩。已經(jīng)屬于卡出翔的節(jié)奏了办陷。
忘掉這種寫法吧,下面介紹正確的高效設(shè)置圓角的姿勢律歼。
為 UIView 添加圓角
這種做法的原理是手動(dòng)畫出圓角民镜。雖然我們之前說過,為普通的視圖直接設(shè)置?cornerRadius?屬性即可险毁。但萬一不可避免的需要使用?masksToBounds制圈,就可以使用下面這種方法,它的核心代碼如下:
func kt_drawRectWithRoundedCorner(radius radius: CGFloat,?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? borderWidth: CGFloat,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? backgroundColor: UIColor,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? borderColor: UIColor)->UIImage{
UIGraphicsBeginImageContextWithOptions(sizeToFit,false,UIScreen.mainScreen().scale)
let context =UIGraphicsGetCurrentContext()
CGContextMoveToPoint(context, 開始位置);// 開始坐標(biāo)右邊開始
CGContextAddArcToPoint(context, x1, y1, x2, y2, radius);// 這種類型的代碼重復(fù)四次CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
let output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output
}
這個(gè)方法返回的是?UIImage辱揭,也就是說我們利用 Core Graphics 自己畫出了一個(gè)圓角矩形离唐。除了一些必要的代碼外,最核心的就是?CGContextAddArcToPoint?函數(shù)问窃。它中間的四個(gè)參數(shù)表示曲線的起點(diǎn)和終點(diǎn)坐標(biāo)亥鬓,最后一個(gè)參數(shù)表示半徑。調(diào)用了四次函數(shù)后域庇,就可以畫出圓角矩形嵌戈。最后再從當(dāng)前的繪圖上下文中獲取圖片并返回。
有了這個(gè)圖片后听皿,我們創(chuàng)建一個(gè)?UIImageView?并插入到視圖層級(jí)的底部:
extension UIView{
????func kt_addCorner(radius radius: CGFloat,
? ? ? ? ? ? ? ? ? ? ? borderWidth: CGFloat,
? ? ? ? ? ? ? ? ? ? ? backgroundColor: UIColor,
? ? ? ? ? ? ? ? ? ? ? borderColor: UIColor){
? ? ? ? ? ? ? ? ? ? ? let imageView = UIImageView(image: kt_drawRectWithRoundedCorner(radius: radius,? ? borderWidth: borderWidth, backgroundColor: backgroundColor,? borderColor: borderColor))
????????????????????self.insertSubview(imageView, atIndex:0)? ?
????}
}
完整的代碼可以在項(xiàng)目中找到熟呛,使用時(shí),你只需要這樣寫:
let view = UIView(frame:CGRectMake(1,2,3,4))??
view.kt_addCorner(radius:6)
為 UIImageView 添加圓角
相比于上面一種實(shí)現(xiàn)方法尉姨,為?UIImageView?添加圓角更為常用庵朝。它的實(shí)現(xiàn)思路是直接截取圖片:
extension UIImage{
????func kt_drawRectWithRoundedCorner(radius radius: CGFloat,_sizetoFit: CGSize)->UIImage{
????????let rect = CGRect(origin:CGPoint(x:0, y:0), size: sizetoFit)UIGraphicsBeginImageContextWithOptions(rect.size,false,UIScreen.mainScreen().scale)
????????CGContextAddPath(UIGraphicsGetCurrentContext(),UIBezierPath(roundedRect: rect, byRoundingCorners:UIRectCorner.AllCorners, cornerRadii:CGSize(width: radius, height: radius)).CGPath)
????????CGContextClip(UIGraphicsGetCurrentContext())
????????self.drawInRect(rect)
????????CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
????????let output = UIGraphicsGetImageFromCurrentImageContext()
????????UIGraphicsEndImageContext()
????????return output? ??
????}
}
圓角路徑直接用貝塞爾曲線繪制,一個(gè)意外的 bonus 是還可以選擇哪幾個(gè)角有圓角效果又厉。這個(gè)函數(shù)的效果是將原來的?UIImage?剪裁出圓角九府。配合著這函數(shù),我們可以為 UIImageView 拓展一個(gè)設(shè)置圓角的方法:
extension UIImageView{/**
? ? / !!!只有當(dāng) imageView 不為nil 時(shí)覆致,調(diào)用此方法才有效果
? ? :param: radius 圓角半徑
? ? */override func kt_addCorner(radius radius: CGFloat){
????????????self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius,self.bounds.size)? ? ????}
}
完整的代碼可以在項(xiàng)目中找到侄旬,使用時(shí),你只需要這樣寫:
let imageView = UIImageView(image:UIImage(name:""))??
imageView.kt_addCorner(radius:6)
提醒
無論使用上面哪種方法煌妈,你都需要小心使用背景顏色儡羔。因?yàn)榇藭r(shí)我們沒有設(shè)置?masksToBounds宣羊,因此超出圓角的部分依然會(huì)被顯示。因此汰蜘,你不應(yīng)該再使用背景顏色仇冯,可以在繪制圓角矩形時(shí)設(shè)置填充顏色來達(dá)到類似效果。
在為?UIImageView?添加圓角時(shí)鉴扫,請(qǐng)確保?image?屬性不是?nil赞枕,否則這個(gè)設(shè)置將會(huì)無效。
實(shí)戰(zhàn)測試
回到 demo 中坪创,測試一下剛剛定義的這兩個(gè)設(shè)置圓角的方法炕婶。首先在?setupContent?方法中把這兩行代碼的注釋取消掉:
imgView1.kt_addCorner(radius:5)??
imgView2.kt_addCorner(radius:5)
然后使用自定義的方法為 label 和 view 設(shè)置圓角:
view.kt_addCorner(radius:6)?
label.kt_addCorner(radius:6)
現(xiàn)在,我們不僅成功的添加了圓角效果莱预,同時(shí)還保證了性能不受影響:
總結(jié)
如果能夠只用?cornerRadius?解決問題柠掂,就不用優(yōu)化。
如果必須設(shè)置?masksToBounds依沮,可以參考圓角視圖的數(shù)量涯贞,如果數(shù)量較少(一頁只有幾個(gè))也可以考慮不用優(yōu)化。
UIImageView?的圓角通過直接截取圖片實(shí)現(xiàn)危喉,其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實(shí)現(xiàn)宋渔。
原文 完
原文地址:https://bestswifter.com/efficient-rounded-corner/
另外插一句,要學(xué)習(xí)一下離屏渲染的話辜限,附上一個(gè)推薦鏈接:http://foggry.com/blog/2015/05/06/chi-ping-xuan-ran-xue-xi-bi-ji/?utm_source=tuicool