iOS 和常見的離屏渲染Say Goodbye!

程昭華發表於2017-12-21

iOS  和常見的離屏渲染Say Goodbye!

移動應用優化到最後主要還是看FPS(頁面流暢程度)效能、記憶體佔用等方面。離屏渲染也是老生常談的一個問題,本文側重點在常見導致離屏渲染的因素及解決方案。

那麼為什麼離屏渲染會引起效能問題?

OpenGL中,GPU螢幕渲染有兩種方式: On-Screen Rendering (當前螢幕渲染)Off-Screen Rendering (離屏渲染) ,當前螢幕渲染不需要額外建立新的快取,也不需要開啟新的上下文,相對於離屏渲染效能更好。但是受當前螢幕渲染的侷限因素限制(只有自身上下文、螢幕快取有限等),當前螢幕渲染有些情況下的渲染解決不了的,就使用到離屏渲染。離屏渲染的整個過程需要切換上下文環境,先從 當前螢幕切換到離屏,等結束後,又要將上下文環境切換回來.這也是為什麼會消耗效能的原因了。

離屏渲染引發因素有 cornerRadius(設定圓角)、shadows(陰影)、masks(遮罩)、edge antialiasing(抗鋸齒)、group opacity(不透明)、shouldRasterize(光柵化) 等,至於檢測離屏渲染的工具 Instruments的Core Animation 就不多說了。本文主要介紹 設定圓角陰影 的方案。

設定圓角

常規做法:

   //只需要設定layer層的兩個屬性
   //設定圓角
   imageView.layer.cornerRadius = imageView.frame.size.width / 2;
   //將多餘的部分切掉
   imageView.layer.masksToBounds = YES;
複製程式碼

這裡提供兩種避免離屏渲染的方案

  • 1.檢視上新增一個子layer到最上層,用於遮蓋該檢視及其子檢視,設定layer的圖片為剛好能夠遮蓋成所需圓角樣子,並且圖片顏色剛好是該檢視父檢視的背景顏色就達到想要的效果。 原文地址 ,該作者寫的很好,封裝了一個UIView的分類,3個API,分別是 設定一個四角圓角,設定一個指定位置的圓角,設定一個帶邊框的圓角github地址
/**
 設定一個四角圓角

 @param radius 圓角半徑
 @param color  圓角背景色
 */
- (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color;

/**
 設定一個普通圓角

 @param radius  圓角半徑
 @param color   圓角背景色
 @param corners 圓角位置
 */
- (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color corners:(UIRectCorner)corners;

/**
 設定一個帶邊框的圓角

 @param cornerRadii 圓角半徑cornerRadii
 @param color       圓角背景色
 @param corners     圓角位置
 @param borderColor 邊框顏色
 @param borderWidth 邊框線寬
 */
- (void)xw_roundedCornerWithCornerRadii:(CGSize)cornerRadii cornerColor:(UIColor *)color corners:(UIRectCorner)corners borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth;
複製程式碼

下載下來這個分類直接拖入工程就可以使用了,呼叫很方便,不過使用的時候會發現,這三個API都需要傳一個引數 cornerColor (父檢視的背景色),所以也造成了這個功能的侷限,即 如果該父檢視的顏色不是純色,此時該方式就不適用了,同樣 如果父檢視的顏色會變化,那實現起來的程式碼也不那麼優雅,如下圖,有點尷尬,這裡引出了第二種方案。

邊角顏色與背景色不符

  • 2.通過修改layer.mask,首先通過貝塞爾曲線建立基於向量的路徑 ,傳遞給CAShapeLayer進行渲染。路徑閉環,再把繪製出的Shape賦值給layer.mask,在Mask範圍之外的Layer將不被顯示從而達到圓角效果。程式碼實現很簡單,如下:
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(130, 330, 100, 100)];
    [btn setBackgroundColor:[UIColor colorWithRed:(226.0 / 255.0) green:(113.0 / 255.0) blue:(19.0 / 255.0) alpha:1]];
    [backgroundImageView addSubview:btn];
    //繪製曲線路徑
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:btn.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:btn.bounds.size];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
    //設定大小
    maskLayer.frame = btn.bounds;
    //設定圖形樣子
    maskLayer.path = maskPath.CGPath;
    btn.layer.mask = maskLayer;
複製程式碼

效果圖:

個人認為第二種方案更簡單而且功能擴充套件性更強些

設定陰影

常規做法:

//陰影的顏色
self.imageView.layer.shadowColor= [UIColorblackColor].CGColor;
//陰影的透明度
self.imageView.layer.shadowOpacity=0.8f;
//陰影的圓角
self.imageView.layer.shadowRadius=4;
//陰影偏移量
self.imageView.layer.shadowOffset=CGSizeMake(0,0);
複製程式碼

優化方案: 避免對shadowOffset直接修改,通過呼叫setShadowPath來提供一個CGPath給檢視的Layer,向Core Animation提供渲染的View的形狀Shape,就會減少離屏渲染計算

[self.imageView.layer setShadowPath:[[UIBezierPath 
    bezierPathWithRect:myView.bounds] CGPath]];
複製程式碼

補充:當使用陰影的檢視形狀發生變化時,即shadowPath並不會跟隨CALayer的bounds屬性進行變化,所以在layer的bounds產生變化以後需要手動更新shadowPath才能讓其適配新的bounds。具體推薦看這篇文章

關於介面流暢如果想要深層探索可以看 YYKit作者 寫的文章iOS 保持介面流暢的技巧 。該文章從螢幕顯示影象的原理,到改進的方案都有詳細介紹。

謝謝各位,歡迎指教!

相關文章