? ? ? ? ? 實際開發中,大多會遇到圓角或者圓形的控件的情況。通常,簡便的解決方案主要是:
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;