? ? ? ? ? 實(shí)際開發(fā)中壕探,大多會(huì)遇到圓角或者圓形的控件的情況琅催。通常圆丹,簡便的解決方案主要是:
1.讓美工做一個(gè)圓角的圖片滩愁,我們直接放圖片就OK。
2.就是常用的layer的那兩個(gè)屬性(cornerRadius , masksToBounds)辫封。
? ? ? ? ? ?第一種方法不說了硝枉,第二種方法,在圓角不多的時(shí)候還可以倦微,如果一個(gè)界面上的圓角控件很多的時(shí)候妻味,再用它就出問題了,欣福。就像下面這種情況的時(shí)候责球,滑動(dòng)tableView就會(huì)明顯感覺到屏幕的卡頓:
究其原因,我們在用masksToBounds這個(gè)屬性的時(shí)候GPU屏幕渲染才用了離屏渲染的方式拓劝。由此雏逾,我們引出了離屏渲染的概念------
? ? ? ? 離屏渲染是什么?
OpenGL中郑临,GPU屏幕渲染有以下兩種方式:
? ? ?1栖博、On-Screen Rendering:
? ? ? ?意思是當(dāng)前屏幕渲染,指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行厢洞。
? ? ?2仇让、Off-Screen Rendering:
? ? ? ?意思就是我們說的離屏渲染了,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作躺翻。
相比于當(dāng)前屏幕渲染丧叽,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面:
一获枝、創(chuàng)建新緩沖區(qū)蠢正,要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)省店。
二嚣崭、上下文切換,離屏渲染的整個(gè)過程懦傍,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen)雹舀;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上有需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕粗俱。而上下文環(huán)境的切換是要付出很大代價(jià)的说榆。
會(huì)導(dǎo)致離屏渲染的幾種情況:
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裝載一個(gè)圓角image來處理,簡單地說就是比如一個(gè)UIView(或其子類)設(shè)置圓角,就在底層鋪一個(gè)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);//準(zhǔn)備開始移動(dòng)坐標(biāo)
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;
}
給一個(gè)圖片做出圓角:
@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 是當(dāng)前路徑所在的點(diǎn),坐標(biāo)是(x,y)
P1(x,y)和(x1,y1)構(gòu)成切線1,(x1,y1)和(x2,y2)構(gòu)成切線2, r 是上面函數(shù)中的radius, 紅色的線就是CGContextAddArcToPoint繪制的曲線. 它不會(huì)畫到 (x2, y2)這個(gè)點(diǎn)唱蒸, 繪制到圓弧的終點(diǎn)就會(huì)停止.?
當(dāng)然邦鲫,在圓角較少的情況下大可不必這么折騰,設(shè)置圓角的方法也有很多種神汹,除了最常用的layer兩個(gè)屬性以外庆捺,還有:
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)]; //這里的第二個(gè)參數(shù)可以設(shè)置圓角的位置,這里是設(shè)置左上和右上兩個(gè)圓角? ?
?CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];??
? maskLayer.frame = self.bounds;??
? maskLayer.path = maskPatch.CGPath;? ?
? self.layer.mask = maskLayer;
}
這里要感謝葉孤城大神的文章解疑屁魏,本文圓角設(shè)置方法介紹不算全面滔以,還有待補(bǔ)充,只是最近項(xiàng)目上遇到了百年一遇的離屏渲染導(dǎo)致了嚴(yán)重卡屏問題所以就小研究了一下氓拼,并做下筆記你画。
----------------------------華麗的時(shí)間分割線------------------------------
2月15日補(bǔ)充:陰影效果的離屏渲染
給UIView及其子類添加陰影效果的時(shí)候,通常我們是這么做的:
//陰影的顏色
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);
這里就像上文所說的桃漾,直接設(shè)置了shadowOffset撬即,因此導(dǎo)致了離屏渲染。
解決辦法:
用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)];
//設(shè)置陰影路徑
self.imageView.layer.shadowPath= path.CGPath;