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 = [[UIImageView alloc] 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 {

CGSize sizeToFit =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);

CGFloat width = 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();

return image;

}
给一个图片做出圆角:

@implementation UIImageView (CornerRounder)

- (void)dr_addCornerRadius:(CGFloat)radius {

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

}

@end
@implementation UIImage (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:UIRectCornerAllCornerscorner Radii:CGSizeMake(radius, radius)];

CGContextAddPath(ctx,path.CGPath);

CGContextClip(ctx);

[self drawInRect:rect];

CGContextDrawPath(ctx,kCGPathFillStroke);

UIImage* newImage =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return newImage;

}

@end

这样就可以就可以有效避免大量的离屏渲染拖慢fps。

其中 CGContextAddArcToPoint用法:

void CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)

下图中,P1 是当前路径所在的点,坐标是(x,y)

P1(x,y)和(x1,y1)构成切线1,(x1,y1)和(x2,y2)构成切线2, r 是上面函数中的radius, 红色的线就是CGContextAddArcToPoint绘制的曲线. 它不会画到 (x2, y2)这个点, 绘制到圆弧的终点就会停止.
这里写图片描述

当然,在圆角较少的情况下大可不必这么折腾,设置圆角的方法也有很多种,除了最常用的layer两个属性以外,还有:

1.UIBezierPath:


CGRect bounds =self.bounds;

[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.

0] addClip];

[self.image drawInRect:bounds];

}

2.maskLayer(CAShapeLayer)这种方法写在drawRect:方法里,最终依然会导致离屏渲染,所以适合在量不大的圆角处理上。

- (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;
}

这里要感谢叶孤城大神的文章解疑,本文圆角设置方法介绍不算全面,还有待补充,只是最近项目上遇到了百年一遇的离屏渲染导致了严重卡屏问题所以就小研究了一下,并做下笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值