圓角(RounderCorner)是一種很常見的視圖效果,相比於直角,它更加柔和優美,易於接受。但不少人並不清楚如何設置圓角的正確方式和原理。設置圓角會帶來必定的性能損耗,如何提升性能是另外一個須要重點討論的話題。我查閱了一些現有的資料,收穫良多的同時也發現了一些誤導人錯誤。本文總結整理了一些知識點,歸納以下:html
設置圓角的正確姿式及其原理ios
設置圓角的性能損耗git
其餘設置圓角的方法,以及最優選擇程序員
我爲本文製做了一個 demo,讀者能夠在個人 github 上 clone 下來:CornerRadius,若是以爲有幫助還望給個star以示支持。項目由 Swift 實現,但請務必相信我即便你只會 Objective-C,也能夠看懂它。由於其中的關鍵知識與 Swift 無關。github
我爲本文製做了一個 demo,讀者能夠在個人 github 上 clone 下來:CornerRadius,若是以爲有幫助還望給個star以示支持。項目由 Swift 實現,但請務必相信我即便你只會 Objective-C,也能夠看懂它。由於其中的關鍵知識與 Swift 無關。web
正確姿式編程
首先,我想要聲明的一點是:設置圓角很簡單,它不會帶來任何性能損耗。swift
由於這件事原本就很簡單,它只須要一行代碼:架構
1
|
view.layer.cornerRadius = 5
|
先別急着關掉網頁,也別急着回覆,咱們讓事實說話。打開 Instuments,選擇 Core Animation 調試,你會發現既沒有 Off-Screen Render,也沒有下降幀數。關於使用 Instuments 分析應用,你能夠參考個人這篇文章:UIKit性能調優實戰講解。從截圖中能夠看到第三個棕色視圖確確實實設置了圓角:app
不過查看一下代碼能夠發現,有一個 UILabel 也設置了圓角,可是沒有表現出任何變化。關於這一點,你能夠查看 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.
也就是說在默認狀況下,這個屬性只會影響視圖的背景顏色和 border。對於 UILabel 這樣內部還有子視圖的控件就無能爲力了。因此不少狀況下咱們會看到這樣的代碼:
1
2
|
label.layer.cornerRadius = 5
label.layer.masksToBounds =
true
|
咱們把第二行代碼添加到 CustomTableViewCell 的構造方法中,再次運行 Instument,就能夠看到圓角效果了。
性能損耗
若是你勾選上 Color Offscreen-Rendered Yellow,就會發現 label 的四周出現了黃色的標記,說明這裏出現了離屏渲染。關於離屏渲染的介紹,一樣能夠參考:UIKit性能調優實戰講解,就不在本文贅述了。
須要強調的一點是,離屏渲染並不是由設置圓角致使的!經過控制變量的方法很容易得出這個結論,由於 UIView 只是設置了 cornerRadius,但它沒有出現離屏渲染。某些比較權威的文章,好比 Stackoverflow 和 CodeReview 都提到設置 cornerRadius 會致使離屏渲染從而影響性能,我想這實在是冤枉了可愛的 cornerRadius 變量,也誤導了別人。
雖然設置 masksToBounds 會致使離屏渲染,從而影響性能,可是這個影響到底會有多大?在個人 iPhone6 上,即便出現了 17 個帶有圓角的視圖,滑動時的幀數依然在 58 - 59 fps 左右波動。
然而,這並不是說明 iOS 9 作了什麼特殊優化,或者是離屏渲染的影響不大,其主要緣由在於圓角不夠多。當我將一個 UIImageView 也設置成圓角,也就是屏幕上的圓角視圖達到 34 個時,fps 大幅度降低,大約只有 33 左右。基本上已經達到了影響用戶體驗的範圍。所以,一切不講依據的優化都是耍流氓,若是你的圓角視圖很少,cell 不復雜,就不要費力氣折騰了。
高效地設置圓角
假設如今圓角視圖很是多(好比在 UICollectionView 中),那麼如何爲視圖高效的添加圓角呢?網上的教程大多沒有說全,由於這個事要分兩種狀況考慮。爲普通的 UIView 設置圓角,和爲 UIImageView 設置圓角的原理大相徑庭。
有一種作法是這樣的,這種寫法試圖實現 cornerRadius = 3 的效果:
1
2
3
4
5
6
7
8
9
|
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
}
|
不過這是一種錯的離譜的寫法!
首先,咱們應該儘可能避免重寫 drawRect 方法。不恰當的使用這個方法會致使內存暴增。舉個例子,iPhone6 上與屏幕等大的 UIView,即便重寫一個空的 drawRect 方法,它也至少佔用 750 * 1134 * 4 字節 ≈ 3.4 Mb 的內存。在內存惡鬼drawRect 及其後續中,做者詳細介紹了其中原理,據他測試,在 iPhone6 上空的、與屏幕等大的視圖重寫 drawRect 方法會消耗 5.2 Mb 內存。總之,能避免重寫 drawRect 方法就儘量避免。
其次,這種方法本質上是用遮罩層 mask 來實現,所以一樣無可避免的會致使離屏渲染。我試着將此前 34 個視圖的圓角改用這種方法實現,結果 fps 掉到 11 左右。已經屬於卡出翔的節奏了。
忘掉這種寫法吧,下面介紹正確的高效設置圓角的姿式。
爲 UIView 添加圓角
這種作法的原理是手動畫出圓角。雖然咱們以前說過,爲普通的視圖直接設置 cornerRadius 屬性便可。但萬一不可避免的須要使用 masksToBounds,就可使用下面這種方法,它的核心代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func kt_drawRectWithRoundedCorner(radius radius: CGFloat,
borderWidth: CGFloat,
backgroundColor: UIColor,
borderColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(sizeToFit,
false
, UIScreen.mainScreen().scale)
let context = UIGraphicsGetCurrentContext()
CGContextMoveToPoint(context, 開始位置);
// 開始座標右邊開始
CGContextAddArcToPoint(context, x1, y1, x2, y2, radius);
// 這種類型的代碼重複四次
CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
let output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return
output
}
|
這個方法返回的是 UIImage,也就是說咱們利用 Core Graphics 本身畫出了一個圓角矩形。除了一些必要的代碼外,最核心的就是 CGContextAddArcToPoint 函數。它中間的四個參數表示曲線的起點和終點座標,最後一個參數表示半徑。調用了四次函數後,就能夠畫出圓角矩形。最後再從當前的繪圖上下文中獲取圖片並返回。
有了這個圖片後,咱們建立一個 UIImageView 並插入到視圖層級的底部:
1
2
3
4
5
6
7
8
9
10
11
12
|
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)
}
}
|
完整的代碼能夠在項目中找到,使用時,你只須要這樣寫:
1
2
|
let view = UIView(frame: CGRectMake(1,2,3,4))
view.kt_addCorner(radius: 6)
|
爲 UIImageView 添加圓角
相比於上面一種實現方法,爲 UIImageView 添加圓角更爲經常使用。它的實現思路是直接截取圖片:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
}
}
|
圓角路徑直接用貝塞爾曲線繪製,一個意外的 bonus 是還能夠選擇哪幾個角有圓角效果。這個函數的效果是將原來的 UIImage 剪裁出圓角。配合着這函數,咱們能夠爲 UIImageView 拓展一個設置圓角的方法:
1
2
3
4
5
6
7
8
9
|
extension UIImageView {
/**
/ !!!只有當 imageView 不爲nil 時,調用此方法纔有效果
:param: radius 圓角半徑
*/
override func kt_addCorner(radius radius: CGFloat) {
self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size)
}
}
|
完整的代碼能夠在項目中找到,使用時,你只須要這樣寫:
1
2
|
let imageView = let imgView1 = UIImageView(image: UIImage(name:
""
))
imageView.kt_addCorner(radius: 6)
|
提醒:
不管使用上面哪一種方法,你都須要當心使用背景顏色。由於此時咱們沒有設置 masksToBounds,所以超出圓角的部分依然會被顯示。所以,你不該該再使用背景顏色,能夠在繪製圓角矩形時設置填充顏色來達到相似效果。
在爲 UIImageView 添加圓角時,請確保 image 屬性不是 nil,不然這個設置將會無效。
實戰測試
回到 demo 中,測試一下剛剛定義的這兩個設置圓角的方法。首先在 setupContent 方法中把這兩行代碼的註釋取消掉:
1
2
|
imgView1.kt_addCorner(radius: 5)
imgView2.kt_addCorner(radius: 5)
|
而後使用自定義的方法爲 label 和 view 設置圓角:
1
2
|
view.kt_addCorner(radius: 6)
label.kt_addCorner(radius: 6)
|
如今,咱們不只成功的添加了圓角效果,同時還保證了性能不受影響:
性能測試
總結
若是可以只用 cornerRadius 解決問題,就不用優化。
若是必須設置 masksToBounds,能夠參考圓角視圖的數量,若是數量較少(一頁只有幾個)也能夠考慮不用優化。
UIImageView 的圓角經過直接截取圖片實現,其它視圖的圓角能夠經過 Core Graphics 畫出圓角矩形實現。
問啊-一鍵呼叫程序員答題神器,牛人一對一服務,開發者編程必備官方網站:www.wenaaa.com
QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!