[譯] iOS | 圓角的處理

前言

原文出自AsyncDisplayKit(如今叫Texture)文檔中的一篇關於圓角的文章:Corner Roundinghtml

圓角的處理

當談到圓角處理,許多開發人員都堅持使用CALayer.cornerRadius屬性。不幸的是,這個使用方便的屬性極大地增長了性能壓力,你應當在沒有其餘選擇時才使用這個屬性纔對。這篇文章將涵蓋:架構

  • 爲何不該該使用CALayer.cornerRadius
  • 更多高性能的圓角設置方式以及什麼時候使用它們
  • 一張告訴你該如何選擇圓角策略的流程圖
  • Texture中設置圓角的樣例

設置.cornerRadius的代價很大

爲何.cornerRadius的代價很大?由於使用CALayer.cornerRadius屬性會在滾動期間爲60FPS的屏幕上觸發離屏渲染(offscreen rendering),即便該區域的內容沒有任何改變。這意味着GPU必須在每一幀上切換上下文(context),包括合成整個幀和每次使用.cornerRadius所致使的附加遍歷之間(?)。app

重要的是,這些消耗不會顯示在Time Profiler中,由於它們會影響到CoreAnimation Render Server幫助App作的工做(?)。這種莽的不行的行爲消耗了許多設備的性能。在iPhone 四、4S和5 / 5C(以及相似的iPad / iPod)上,你能性能顯着降低。在更新版本的iPhone上,即便你看不到直接的影響,它也會使內存空間減小,從而更容易產生掉幀的狀況。性能

圓角的高性能設置策略

選擇圓角設置策時只須要考慮三件事:優化

  • 在圓角下方有移動嘛?
  • 在圓角處有移動麼?
  • 四個圓角都屬於同一個節點? 而且 有沒有其餘節點在圓角區域相交?

譯者注:這裏的節點指的是AsyncDisplayKitTexture)中的最基本單位,至關於UIKit中的UIView動畫

圓角下方的移動指的是一切在圓角圖層下方的移動。例如,當一個有圓角的collection view cell在背景圖層上滾動時,背景將在圓角底下移動並移出圓角。spa

至於圓角處的移動,請想象一個較小的帶圓角的scroll view中包含了一張很大的圖片。在scroll view內部縮放和平移圖片時,圖片將在scroll view的各個圓角處移動。設計

上圖將圓角下方的移動高亮爲藍色而且將圓角處的移動高亮爲橙色。code

提示:在圓角對象內部能夠有無需通過圓角的移動。下圖展現了一塊綠色高亮的區域,與scroll view邊框有一個等同於圓角角度的內邊距,當這塊區域滾動時,就不算是在圓角處移動。cdn

根據上述的說法來調整你的設計,消除其中的一種圓角移動,能讓你在使用快速圓角技術時和使用.cornerRadius時產生巨大區別(?)。

最後要考慮的是肯定全部四個角是否都在同一節點,或者是否有任何子節點與圓角區域相交。

預合成圓角

預合成的圓角是指使用貝塞爾曲線路徑在CGContext/UIGraphicsContext中剪切內容(path.clip)所繪製的圓角。 在這種狀況下,拐角將成爲圖像自己的一部分,並被整合到單個CALayer中。 有兩種類型的預合成圓角。

最佳的方法是使用預合成的不透明角。這是可用的最有效方法,能夠作到無Alpha混合(儘管這比起離屏渲染沒那麼重要),那不幸的是,這種方法最不靈活。若是圓角圖像須要在某個背景上移動,則這個背景將須要爲純色才行。有一個小技巧是,你可使用帶紋理背景或照片背景來製做預合成圓角的,但一般來講你最好使用預合成的帶Alpha圓角

第二種方法是涉及有預合成的帶Alpha圓角的貝塞爾曲線路徑,此方法很是靈活,應該是最經常使用的方法之一。這個方法確實會以整個內容的大小,產生Alpha混合的消耗,而且由於Alpha通道,會比不透明的預合成增長多25%的內存消耗。但這些消耗對於現代設備來講已經很小了,而且這和啓動.cornerRadius致使離屏渲染所產生的消耗來講根本不是一個數量級的。

預合成圓角的一個關鍵限制是,圓角只能接觸一個節點,而不能與任何子節點相交。若是存在以上任何一種狀況,則必須使用clip corner。

請注意,在Texture中節點對.cornerRadius有特殊的優化,只有當你使用了.shouldRasterizeDescendants後會自動實現預合成角。在啓用柵格化以前,請務必仔細考慮,所以,在未徹底瞭解該概念以前,請勿使用此選項。

若是你想要一個簡單的,純色的圓角矩形或圓形,Texture爲你提供了一些便利方法。請參閱UIImage + ASConveniences.h,以瞭解使用預合成的角(支持Alpha和不透明)建立純色、圓角可調整大小的圖像的方法。這些很是適合用做ASButtonNode的背景或是圖片節點的佔位符。

切割角

該方法是將4個獨立的不透明角放置在須要圓角的區域上。該方法靈活,且有很好的性能。4個獨立的layer消耗較小的CPU功率,一個layer對應一個圓角。

切割角主要運用於兩種圓角狀況:

  • 圓角接觸多個節點或與任何子節點相交的狀況。
  • 在固定的紋理或照片背景上的圓角。切割角方法很刁鑽,但頗有用!

可使用.cornerRadius嗎?

在不多數狀況下,是適合使用.cornerRadius的,其中包括一個狀況是一個圓角內和圓角下都須要移動的動態區域。對於某些動畫,這是不可避免的。可是,在許多狀況下,很容易經過調整設計來消除這樣兩種移動中的一種。在圓角移動一節中討論了一種這樣的狀況。

當你屏幕上的內容不怎麼移動時,使用.cornerRadius或是把它做爲一個簡易實現方式也沒有那麼糟糕。可是,當屏幕上出現了移動,即便這個移動的區域不包含圓角,也會致使額外的性能負擔。例如,在導航欄中具備一個圓形元素,並在其下方有一個scroll view,即便它們不重疊,也會有影響。屏幕上的全部內容會進行動畫處理,即便用戶不進行交互。另外,任何形式的屏幕刷新都會消耗關於圓角切割的性能。

柵格化和圖層支持

有人建議使用CALayer.shouldRasterize能夠提升.cornerRadius屬性的性能。這是一個沒有很好理解清楚的選擇,是很危險的。當沒有東西致使圖層從新柵格化(沒有移動,沒有點擊更改顏色,不在會移動的tableView上等等),就可使用。一般,咱們不鼓勵這樣作,由於這很容易致使性能更加降低。對於不具有出色應用程序架構並堅持使用CALayer.cornerRadius(例如,他們的應用程序性能不佳)的用戶,這可能能夠帶來有意義的變化。可是,若是您是從頭開始構建你的app的話,咱們強烈建議您選擇上述更好的圓角策略之一。

CALayer.shouldRasterizeTexture中節點的.shouldRasterizeDescendents無關。啓用後,.shouldRasterizeDescendents將阻止子節點的實際視圖和圖層的建立。

圓角策略流程圖

使用此流程圖選擇性能最佳的策略來解決圓角問題。

Texture的支持

如下代碼舉例說明了如何在Texture中使用圓角的不一樣方法:

使用.cornerRadius

var cornerRadius: CGFloat = 20.0

photoImageNode.cornerRoundingType = ASCornerRoundingTypeDefaultSlowCALayer
photoImageNode.cornerRadius = cornerRadius
複製代碼

使用預合成圓角

var cornerRadius: CGFloat = 20.0

// Use precomposition for rounding corners
photoImageNode.cornerRoundingType = ASCornerRoundingTypePrecomposited
photoImageNode.cornerRadius = cornerRadius
複製代碼

使用切割角

var cornerRadius: CGFloat = 20.0

photoImageNode.cornerRoundingType = ASCornerRoundingTypeClipping
photoImageNode.backgroundColor = UIColor.white
photoImageNode.cornerRadius = cornerRadius
複製代碼

使用willDisplayNodeContentWithRenderingContext來設置某區域圓角的切割路徑

var cornerRadius: CGFloat = 20.0

// Use the screen scale for corner radius to respect content scale
var screenScale: CGFloat = UIScreen.main.scale
photoImageNode.willDisplayNodeContentWithRenderingContext = { context, drawParameters in
    var bounds: CGRect = context.boundingBoxOfClipPath()
    var radius: CGFloat = cornerRadius * screenScale
    var overlay = UIImage.as_resizableRoundedImage(withCornerRadius: radius, cornerColor: UIColor.clear, fill: UIColor.clear)
    overlay.draw(in: bounds)
    UIBezierPath(roundedRect: bounds, cornerRadius: radius).addClip()
}
複製代碼

使用ASImageNode來給圖片加圓角和邊框。這是一個給頭像添加圓角的好例子。

var cornerRadius: CGFloat = 20.0

photoImageNode.imageModificationBlock = ASImageNodeRoundBorderModificationBlock(5.0, UIColor.orange)
複製代碼
相關文章
相關標籤/搜索