設(shè)計趨勢
隨著移動互聯(lián)網(wǎng)的發(fā)展,在UI設(shè)計領(lǐng)域,圓角和陰影逐漸成為一種不可或缺的設(shè)計手法,這離不開系統(tǒng)廠商對于設(shè)計風(fēng)格的引導(dǎo),比如:Apple產(chǎn)品的設(shè)計風(fēng)格和UI設(shè)計規(guī)范、Google Material Design等等。不可否認,圓角設(shè)計確實會增強產(chǎn)品的親和力和優(yōu)雅感,但是作為開發(fā)者,我們卻不那么喜歡它,因為通常大量圓角+陰影會顯著降低界面滑動的流暢度。

是什么造成了卡頓
有沒有圓角和陰影對于CPU來說沒有太大差別,額外的效果會給GPU帶來較大的壓力。通過Xcode提供的,View debugging可以看到,有沒有圓角在渲染上的差別在于離屏渲染。離屏渲染就是圖層無法直接在當前屏幕繪制,而要開辟新的緩沖區(qū)進行繪制,然后在切換到當前緩沖區(qū)進行展示,中間就包含了不少性能消耗。
離屏渲染發(fā)生與否取決于技術(shù)實現(xiàn),iOS在發(fā)展過程中,也在盡量的減少離屏渲染發(fā)的發(fā)生。例如:在iOS9上設(shè)置了圓角并且maskToBounds就會觸發(fā)離屏渲染,而在iOS11以后則需要在子視圖也設(shè)置了圓角時才會觸發(fā)。所以最好的辦法就是通過Xcode提供的View debugging來查看。
優(yōu)化之預(yù)合成圓角
在圓角優(yōu)化方面,目前主流的技術(shù)方案是,圖片下載下來后根據(jù)圓角大小來重新繪制一張切過圓角的PNG圖片,然后將此圖片展示到UI控件上,這樣控件就不必再設(shè)置圓角。

預(yù)合成圓角避免了離屏渲染,性能表現(xiàn)很不錯,但是依然存在幾點問題:
- 如果控件是Gif或者視頻,預(yù)先渲染將無法實施
- 每張圖片都需要重新繪制,會帶來額外的CPU開銷
- 原始圖片和重繪后的圖片都需要存入緩存,帶來較大的內(nèi)存占用
- 同樣一張圖片,如果展示的尺寸和圓角值不一樣,則需要重繪多個版本
- 通常圖標上會加入子視圖,無法被天然裁切

換個思路,探索適用于90%場景的障眼之法
雖然我們看到的是圓角,但可能實際不是個圓角。相信有人用過這種方式生成圓角效果,簡單高效,通過圖層疊加,讓圖片看起來像是有圓角。這種方案的問題在于:不同尺寸、圓角大小、描邊及陰影效果都需要提供單獨的圖片,通用性非常不好。

拋開問題不說,這條技術(shù)路線有沒有價值呢?為此統(tǒng)計了大多數(shù)app中的圓角設(shè)計,發(fā)現(xiàn)絕大多數(shù)圓角陰影效果都可以通過這種方式實現(xiàn)。

不能實現(xiàn)的是背景非純色的場景

所以總體看來基于圖層疊加的圓角優(yōu)化技術(shù)有其獨特價值。
圓角與陰影的實現(xiàn)
利用CoreGraphics可以繪制圖片的圓角、陰影和描邊,具體的不再展開。


尺寸的自適應(yīng)
關(guān)鍵問題來了,這個蒙層要繪制多大?
通常一套頁面設(shè)計中,控件都使用一致的圓角和陰影效果,但是控件尺寸會有不同。如果每個尺寸的控件都要繪制一套蒙層,雖然技術(shù)上可以實現(xiàn),但是不免讓人遺憾。
經(jīng)過反復(fù)求證,發(fā)現(xiàn)求得一個最小尺寸,來滿足更大尺寸的自適應(yīng)是可行的,這就解決了一張圖適用多個尺寸的問題。

受限于cornerRadius、offset、blur的大小,自適應(yīng)蒙層有一個自適應(yīng)最小值,當控件尺寸小于蒙層尺寸時則需通過另一套策略繪制,在工程實踐中還沒有遇到這種場景,這一塊暫時沒有支持。
CALayer中關(guān)于自適應(yīng)的設(shè)置,contentCenter的范圍是0~1.0
@property CGRect contentsCenter
SKCornerLayer支持的效果
圓角 陰影 描邊 異步 緩存 DarkMode
使用方法
// 設(shè)置陰影的顏色、blur、offset、圓角支持dark mode
self.shadowView.layer.skCorner
.sShadowColor(colorDynamicFromRGBA(0x535459, 0.15, 0x0, 0))
.sShadowBlur(15)
.sShadowOffset(CGSizeMake(0, 2))
.sCornerRadiusAll(8)
.sCornerBackgroundColor(colorDynamicFromRGB(0xffffff, 0x1e1e1e));
// 異步繪制
self.shadowView.layer.skCornerLayer.displayCornerAsynchronously = YES;
性能量化
這里構(gòu)了一個場景,左邊使用SKCornerLayer,右邊使用默認的圓角+陰影(采用了默認最優(yōu)配置:圓角view沒有嵌套,陰影指定shadowPath,沒有產(chǎn)生離屏渲染)。測試中采用,通過timer以相同的速度滾動列表,以產(chǎn)生穩(wěn)定的GPU占用率。


在iOS9上即使圓角后的view沒有嵌套,也會產(chǎn)生離屏渲染,GPU開銷會更大
測試設(shè)備iPhone 6sPlus iOS12.2
這里設(shè)置了兩個對比組,blur分別為15和5,分別可以提升GPU性能約40%和28%,總體來看blur值越大優(yōu)化效果越好。

關(guān)于圓角和陰影的一些建議
- 父視圖有圓角時避免子視圖包含圓角效果,解決辦法是,視圖層級扁平化
- 對性能要求不高的場景下,通過指定shadowPath來生成陰影也是個不錯的選擇
- 對性能有較高要求的場景下,建議使用SKCornerLayer或者切圖來構(gòu)建陰影
- 最后使用View debugging檢查視圖