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阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容