iOS的離屏渲染

? ? ? ? ? 實際開發中,大多會遇到圓角或者圓形的控件的情況。通常,簡便的解決方案主要是:

1.讓美工做一個圓角的圖片,我們直接放圖片就OK。

2.就是常用的layer的那兩個屬性(cornerRadius , masksToBounds)。

? ? ? ? ? ?第一種方法不說了,第二種方法,在圓角不多的時候還可以,如果一個界面上的圓角控件很多的時候,再用它就出問題了,。就像下面這種情況的時候,滑動tableView就會明顯感覺到屏幕的卡頓:


究其原因,我們在用masksToBounds這個屬性的時候GPU屏幕渲染才用了離屏渲染的方式。由此,我們引出了離屏渲染的概念------

? ? ? ? 離屏渲染是什么?

OpenGL中,GPU屏幕渲染有以下兩種方式:

? ? ?1、On-Screen Rendering:

? ? ? ?意思是當前屏幕渲染,指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區進行。

? ? ?2、Off-Screen Rendering:

? ? ? ?意思就是我們說的離屏渲染了,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。


相比于當前屏幕渲染,離屏渲染的代價是很高的,主要體現在兩個方面:

一、創建新緩沖區,要想進行離屏渲染,首先要創建一個新的緩沖區。

二、上下文切換,離屏渲染的整個過程,需要多次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以后,將離屏緩沖區的渲染結果顯示到屏幕上有需要將上下文環境從離屏切換到當前屏幕。而上下文環境的切換是要付出很大代價的。

會導致離屏渲染的幾種情況:

1. custom drawRect: (any, even if you simply fill the background with color)

2. CALayer shadow

3. CALayer mask

4. any custom drawing using CGContext

解決圓角離屏渲染的方案:

圓角使用UIImageView裝載一個圓角image來處理,簡單地說就是比如一個UIView(或其子類)設置圓角,就在底層鋪一個UIImageView,然后用GraphicsContext生成一張帶圓角的圖片放在UIImageView上。

#import "DrCorner.h"

@interface DrCorner()

@end

@implementation DrCorner

+ (CGFloat)ceilbyunit:(CGFloat)num unit:(double)unit {

return num -modf(num, &unit) + unit;

}

+ (CGFloat)floorbyunit:(CGFloat)num unit:(double)unit {

return num -modf(num, &unit);

}

+ (CGFloat)roundbyunit:(CGFloat)num unit:(double)unit {

CGFloat remain =modf(num, &unit);

if (remain > unit /2.0) {

return [self ceilbyunit:num unit:unit];

}else{

return [self floorbyunit:num unit:unit];

}

}

+ (CGFloat)pixel:(CGFloat)num {

CGFloat unit;

CGFloat scale = [[UIScreen mainScreen] scale];

switch((NSInteger)scale) {

case 1:

unit =1.0/1.0;

break;

case 2:

unit =1.0/2.0;

break;

case 3:

unit =1.0/3.0;

break;

default:

unit =0.0;

break;

}

return [self roundbyunit:num unit:unit];

}

@end

- (void)dr_addCornerRadius:(CGFloat)radius

borderWidth:(CGFloat)borderWidth

backgroundColor:(UIColor*)backgroundColor

borderCorlor:(UIColor*)borderColor {

UIImageView *imageView = [[UIImage Viewalloc] initWithImage:[self? dr_drawRectWithRoundedCornerRadius:radius

?borderWidth:borderWidth

backgroundColor:backgroundColor

borderCorlor:borderColor]];

[self insertSubview:imageView atIndex:0];

}

- (UIImage*)dr_drawRectWithRoundedCornerRadius:(CGFloat)radius

borderWidth:(CGFloat)borderWidth

backgroundColor:(UIColor*)backgroundColor

borderCorlor:(UIColor*)borderColor {

CGSizesizeToFit =CGSizeMake([DrCorner pixel:self.bounds.size.width],self.bounds.size.height);

CGFloat halfBorderWidth = borderWidth /2.0;

UIGraphicsBeginImageContextWithOptions(sizeToFit,NO, [UIScreen mainScreen].scale);

CGContextRefcontext =UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, borderWidth);

CGContextSetStrokeColorWithColor(context, borderColor.CGColor);

CGContextSetFillColorWithColor(context, backgroundColor.CGColor);

CGFloatwidth = sizeToFit.width, height = sizeToFit.height;

CGContextMoveToPoint(context, width - halfBorderWidth, radius + halfBorderWidth);//準備開始移動坐標

CGContextAddArcToPoint(context, width - halfBorderWidth, height - halfBorderWidth, width - radius - halfBorderWidth, height - halfBorderWidth, radius);

CGContextAddArcToPoint(context, halfBorderWidth, height - halfBorderWidth, halfBorderWidth, height - radius - halfBorderWidth, radius);//左下角角度

CGContextAddArcToPoint(context, halfBorderWidth, halfBorderWidth, width - halfBorderWidth, halfBorderWidth, radius);//左上角

CGContextAddArcToPoint(context, width - halfBorderWidth, halfBorderWidth, width - halfBorderWidth, radius + halfBorderWidth, radius);

CGContextDrawPath(UIGraphicsGetCurrentContext(),kCGPathFillStroke);

UIImage*image =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returnimage;

}

給一個圖片做出圓角:

@implementationUIImageView (CornerRounder)

- (void)dr_addCornerRadius:(CGFloat)radius {

self.image= [self.image dr_imageAddCornerWithRadius:radius andSize:self.bounds.size];

}

@end

@implementationUIImage (ImageCornerRounder)

- (UIImage*)dr_imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{

CGRect rect =CGRectMake(0,0, size.width, size.height);

UIGraphicsBeginImageContextWithOptions(size,NO, [UIScreen mainScreen].scale);

CGContextRefctx =UIGraphicsGetCurrentContext();

UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];

CGContextAddPath(ctx,path.CGPath);

CGContextClip(ctx);

[self drawInRect:rect];

CGContextDrawPath(ctx,kCGPathFillStroke);

UIImage* newImage =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returnnewImage;

}

@end

這樣就可以就可以有效避免大量的離屏渲染拖慢fps。

其中 CGContextAddArcToPoint用法:

void CGContextAddArcToPoint(CGContextRef c,CGFloatx1,CGFloaty1,CGFloatx2,CGFloaty2,CGFloatradius)

下圖中,P1 是當前路徑所在的點,坐標是(x,y)

P1(x,y)和(x1,y1)構成切線1,(x1,y1)和(x2,y2)構成切線2, r 是上面函數中的radius, 紅色的線就是CGContextAddArcToPoint繪制的曲線. 它不會畫到 (x2, y2)這個點, 繪制到圓弧的終點就會停止.?



當然,在圓角較少的情況下大可不必這么折騰,設置圓角的方法也有很多種,除了最常用的layer兩個屬性以外,還有:

1.UIBezierPath:

- (void)drawRect:(CGRect)rect {

CGRect bounds =self.bounds;

[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.

0] addClip];

[self.image drawInRect:bounds];

}

2.maskLayer(CAShapeLayer)

- (void)drawRect:(CGRect)rect {

? UIBezierPath *maskPatch = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(10, 10)]; //這里的第二個參數可以設置圓角的位置,這里是設置左上和右上兩個圓角? ?

?CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];??

? maskLayer.frame = self.bounds;??

? maskLayer.path = maskPatch.CGPath;? ?

? self.layer.mask = maskLayer;

}

這里要感謝葉孤城大神的文章解疑,本文圓角設置方法介紹不算全面,還有待補充,只是最近項目上遇到了百年一遇的離屏渲染導致了嚴重卡屏問題所以就小研究了一下,并做下筆記。

----------------------------華麗的時間分割線------------------------------

2月15日補充:陰影效果的離屏渲染

給UIView及其子類添加陰影效果的時候,通常我們是這么做的:

//陰影的顏色

self.imageView.layer.shadowColor= [UIColor blackColor].CGColor;

//陰影的透明度

self.imageView.layer.shadowOpacity=0.8f;

//陰影的圓角

self.imageView.layer.shadowRadius=4;

//陰影偏移量

self.imageView.layer.shadowOffset=CGSizeMake(0,0);

這里就像上文所說的,直接設置了shadowOffset,因此導致了離屏渲染。

解決辦法:

用shadowPath代替。

self.imageView.layer.shadowPath = CGPathCreateWithRect(self.imageView.layer.bounds, nil);

或者可以自定義路徑陰影

UIBezierPath*path = [UIBezierPath bezierPath];

[path moveToPoint:CGPointMake(-5, -5)];

//添加直線

[path addLineToPoint:CGPointMake(imageWidth /2, -15)];

[path addLineToPoint:CGPointMake(imageWidth +5, -5)];

[path addLineToPoint:CGPointMake(imageWidth +15, imageHeight /2)];

[path addLineToPoint:CGPointMake(imageWidth +5, imageHeight +5)];

[path addLineToPoint:CGPointMake(imageWidth /2, imageHeight +15)];

[path addLineToPoint:CGPointMake(-5, imageHeight +5)];

[path addLineToPoint:CGPointMake(-15, imageHeight /2)];

[path addLineToPoint:CGPointMake(-5, -5)];

//設置陰影路徑

self.imageView.layer.shadowPath= path.CGPath;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容