[iOS] 高效添加圓角效果

?本文為搬運(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皇拣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子薄嫡,更是在濱河造成了極大的恐慌氧急,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫深,死亡現(xiàn)場離奇詭異吩坝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哑蔫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門钉寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闸迷,你說我怎么就攤上這事嵌纲。” “怎么了稿黍?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長崩哩。 經(jīng)常有香客問我巡球,道長言沐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任酣栈,我火速辦了婚禮险胰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矿筝。我一直安慰自己起便,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布窖维。 她就那樣靜靜地躺著榆综,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铸史。 梳的紋絲不亂的頭發(fā)上鼻疮,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音琳轿,去河邊找鬼判沟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛崭篡,可吹牛的內(nèi)容都是我干的挪哄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼琉闪,長吁一口氣:“原來是場噩夢啊……” “哼迹炼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起塘偎,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤疗涉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吟秩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咱扣,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年涵防,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闹伪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壮池,死狀恐怖偏瓤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椰憋,我是刑警寧澤厅克,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站橙依,受9級(jí)特大地震影響证舟,放射性物質(zhì)發(fā)生泄漏硕旗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一女责、第九天 我趴在偏房一處隱蔽的房頂上張望漆枚。 院中可真熱鬧,春花似錦抵知、人聲如沸墙基。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽残制。三九已至,卻和暖如春吱肌,著一層夾襖步出監(jiān)牢的瞬間痘拆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工氮墨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纺蛆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓规揪,卻偏偏與公主長得像桥氏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猛铅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354