iOS和常見(jiàn)的離屏渲染Say Goodbye!
移動(dòng)應(yīng)用優(yōu)化到***主要還是看FPS(頁(yè)面流暢程度)性能、內(nèi)存占用等方面。離屏渲染也是老生常談的一個(gè)問(wèn)題,本文側(cè)重點(diǎn)在常見(jiàn)導(dǎo)致離屏渲染的因素及解決方案。
那么為什么離屏渲染會(huì)引起性能問(wèn)題?
OpenGL中,GPU屏幕渲染有兩種方式: On-Screen Rendering (當(dāng)前屏幕渲染) 和 Off-Screen Rendering (離屏渲染) ,當(dāng)前屏幕渲染不需要額外創(chuàng)建新的緩存,也不需要開(kāi)啟新的上下文,相對(duì)于離屏渲染性能更好。但是受當(dāng)前屏幕渲染的局限因素限制(只有自身上下文、屏幕緩存有限等),當(dāng)前屏幕渲染有些情況下的渲染解決不了的,就使用到離屏渲染。離屏渲染的整個(gè)過(guò)程需要切換上下文環(huán)境,先從 當(dāng)前屏幕切換到離屏,等結(jié)束后,又要將上下文環(huán)境切換回來(lái).這也是為什么會(huì)消耗性能的原因了。
離屏渲染引發(fā)因素有 cornerRadius(設(shè)置圓角)、shadows(陰影)、masks(遮罩)、edge antialiasing(抗鋸齒)、group opacity(不透明)、shouldRasterize(光柵化) 等,至于檢測(cè)離屏渲染的工具 Instruments的Core Animation 就不多說(shuō)了。本文主要介紹 設(shè)置圓角 和 陰影 的方案。
設(shè)置圓角
常規(guī)做法:
- //只需要設(shè)置layer層的兩個(gè)屬性
- //設(shè)置圓角
- imageView.layer.cornerRadius = imageView.frame.size.width / 2;
- //將多余的部分切掉
- imageView.layer.masksToBounds = YES;
這里提供兩種避免離屏渲染的方案
1.視圖上添加一個(gè)子layer到最上層,用于遮蓋該視圖及其子視圖,設(shè)置layer的圖片為剛好能夠遮蓋成所需圓角樣子,并且圖片顏色剛好是該視圖父視圖的背景顏色就達(dá)到想要的效果。
- /**
- 設(shè)置一個(gè)四角圓角
- @param radius 圓角半徑
- @param color 圓角背景色
- */
- - (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color;
- /**
- 設(shè)置一個(gè)普通圓角
- @param radius 圓角半徑
- @param color 圓角背景色
- @param corners 圓角位置
- */
- - (void)xw_roundedCornerWithRadius:(CGFloat)radius cornerColor:(UIColor *)color corners:(UIRectCorner)corners;
- /**
- 設(shè)置一個(gè)帶邊框的圓角
- @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;
下載下來(lái)這個(gè)分類直接拖入工程就可以使用了,調(diào)用很方便,不過(guò)使用的時(shí)候會(huì)發(fā)現(xiàn),這三個(gè)API都需要傳一個(gè)參數(shù) cornerColor (父視圖的背景色),所以也造成了這個(gè)功能的局限,即 如果該父視圖的顏色不是純色,此時(shí)該方式就不適用了,同樣 如果父視圖的顏色會(huì)變化,那實(shí)現(xiàn)起來(lái)的代碼也不那么優(yōu)雅,如下圖,有點(diǎn)尷尬,這里引出了第二種方案。
2.通過(guò)修改layer.mask,首先通過(guò)貝塞爾曲線創(chuàng)建基于矢量的路徑,傳遞給CAShapeLayer進(jìn)行渲染。路徑閉環(huán),再把繪制出的Shape賦值給layer.mask,在Mask范圍之外的Layer將不被顯示從而達(dá)到圓角效果。代碼實(shí)現(xiàn)很簡(jiǎn)單,如下:
- 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];
- //設(shè)置大小
- maskLayer.frame = btn.bounds;
- //設(shè)置圖形樣子
- maskLayer.path = maskPath.CGPath;
- btn.layer.mask = maskLayer;
效果圖:
個(gè)人認(rèn)為第二種方案更簡(jiǎn)單而且功能擴(kuò)展性更強(qiáng)些
設(shè)置陰影
常規(guī)做法:
- //陰影的顏色
- self.imageView.layer.shadowColor= [UIColorblackColor].CGColor;
- //陰影的透明度
- self.imageView.layer.shadowOpacity=0.8f;
- //陰影的圓角
- self.imageView.layer.shadowRadius=4;
- //陰影偏移量
- self.imageView.layer.shadowOffset=CGSizeMake(0,0);
優(yōu)化方案:
避免對(duì)shadowOffset直接修改,通過(guò)調(diào)用setShadowPath來(lái)提供一個(gè)CGPath給視圖的Layer,向Core Animation提供渲染的View的形狀Shape,就會(huì)減少離屏渲染計(jì)算
- [self.imageView.layer setShadowPath:[[UIBezierPath
- bezierPathWithRect:myView.bounds] CGPath]];
補(bǔ)充:當(dāng)使用陰影的視圖形狀發(fā)生變化時(shí),即shadowPath并不會(huì)跟隨CALayer的bounds屬性進(jìn)行變化,所以在layer的bounds產(chǎn)生變化以后需要手動(dòng)更新shadowPath才能讓其適配新的bounds。