本文一共介绍 5 种方法裁剪圆角,并且分析其利弊
目录
- cornerRadius + masksToBounds
- 在文本视图类上实现圆角
- UILabel
- UITextField
- UITextView
- 混合图层
- Quartz2D
- Mask 遮罩实现
序言
在实际开发项目中,有很多场景需要裁剪圆角,如果实现方式不当,对性能会造成很大影响,本文就针对如何裁剪圆角做一个汇总。
一 cornerRadius + masksToBounds
设置圆角:
view.layer.cornerRadius = 5
文档中cornerRadius属性的说明:
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to YES causes the content to be clipped to the rounded corners.
很明了,只对前景框和背景色起作用,再看 CALayer 的结构,如果contents有内容或者内容的背景不是透明的话,还需要把这部分弄个角出来,不然合成的结果还是没有圆角,所以才要修改masksToBounds为true(在 UIView 上对应的属性是clipsToBounds,在 IB 里对应的设置是「Clip Subiews」选项)。前些日子很热闹的圆角优化文章中的2篇指出是修改masksToBounds为true而非修改cornerRadius才是触发离屏渲染的原因,但如果以「Color Offscreen-Renderd Yellow」的特征为标准的话,
这两个属性单独作用时都不是引发离屏渲染的原因,他俩合体(masksToBounds = true, cornerRadius>0)才是。
- 实例代码
// 裁剪圆角
- (void)clipRoundedCornerImage {
// 图片
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
iconImgV.layer.cornerRadius = 100;
iconImgV.layer.masksToBounds = YES;
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.top.equalTo(self.view.mas_top).offset(100);
make.centerX.equalTo(self.view);
}];
// 视图
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
redView.backgroundColor = [UIColor redColor];
redView.layer.cornerRadius = 100;
redView.layer.masksToBounds = YES;
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(redView.size);
make.top.equalTo(iconImgV.mas_bottom).offset(50);
make.centerX.equalTo(self.view);
}];
}
运行结果
总结:这种方法最简洁,代码量最少,但是性能最低,因为会触发
离屏渲染
。对性能要求不是很高的场合可以使用。
二 在文本视图类上实现圆角
文本视图主要是这三类:UILabel, UITextField, UITextView。其中 UITextField 类自带圆角风格的外型,UILabel 和 UITextView 要想显示圆角需要表现出与周围不同的背景色才行。想要在 UILabel 和 UITextView 上实现低成本的圆角(不触发离屏渲染),需要保证 layer 的contents呈现透明的背景色,文本视图类的 layer 的contents默认是透明的(字符就在这个透明的环境里绘制、显示),此时只需要设置 layer 的backgroundColor,再加上cornerRadius就可以搞定了。不过 UILabel 上设置backgroundColor的行为被更改了,不再是设定 layer 的背景色而是为contents设置背景色,UITextView 则没有改变这一点。
2.1 UILabel
- 实例代码如下
- (void)drawUI {
UILabel *radiusLbe = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
radiusLbe.textColor = [UIColor blackColor];
radiusLbe.text = @"裁剪圆角";
radiusLbe.textAlignment = NSTextAlignmentCenter;
// a裁剪圆角
radiusLbe.layer.backgroundColor = [[UIColor orangeColor] CGColor];
radiusLbe.layer.cornerRadius = 10;
[self.view addSubview:radiusLbe];
}
效果如下
总结:UILabel设置圆角时,只需要设置
cornerRadius
的值即可,不会触发离屏渲染
。
2.2 UITextField
- 实例代码如下
- (void)drawTextF {
UITextField *textF = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
textF.textColor = [UIColor blackColor];
textF.text = @"裁剪圆角";
textF.textAlignment = NSTextAlignmentCenter;
// a裁剪圆角
textF.layer.backgroundColor = [[UIColor greenColor] CGColor];
textF.layer.cornerRadius = 10;
[self.view addSubview:textF];
}
运行效果如下
总结:UITextField设置圆角时,只需要设置
cornerRadius
的值即可,不会触发离屏渲染
。
2.3 UITextView
- 实例代码如下
- (void)drawTextV {
UITextView *textV = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 20, 200)];
textV.textColor = [UIColor whiteColor];
textV.text = @"如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n";
textV.textAlignment = NSTextAlignmentCenter;
// a裁剪圆角
textV.layer.backgroundColor = [[UIColor blueColor] CGColor];
textV.layer.cornerRadius = 10;
[self.view addSubview:textV];
}
运行效果如下:
总结:UITextView设置圆角时,只需要设置
cornerRadius
的值即可,不会触发离屏渲染
。
三 混合图层
我们可以在需要裁剪的视图上面添加一个已经裁剪好的视图,达到显示圆角的效果。可以让美工提供一张已经是圆角的图片,但是局限性比较大,因为尺寸固定。
下面介绍如何动态生成一个任意尺寸,任意圆角的遮罩图片
3.1 裁剪成圆角
- 实例代码如下
- (void)drawRoundedCornerImage {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.top.equalTo(self.view.mas_top).offset(500);
make.centerX.equalTo(self.view);
}];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[self.view addSubview:imgView];
[imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(imgView.size);
make.top.equalTo(iconImgV.mas_top);
make.leading.equalTo(iconImgV.mas_leading);
}];
// 圆形
imgView.image = [self drawCircleRadius:100 outerSize:CGSizeMake(200, 200) fillColor:[UIColor whiteColor]];
}
// 绘制圆形
- (UIImage *)drawCircleRadius:(float)radius outerSize:(CGSize)outerSize fillColor:(UIColor *)fillColor {
UIGraphicsBeginImageContextWithOptions(outerSize, false, [UIScreen mainScreen].scale);
// 1、获取当前上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路径
// ArcCenter:中心点 radius:半径 startAngle起始角度 endAngle结束角度 clockwise:是否逆时针
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(outerSize.width * 0.5, outerSize.height * 0.5) radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
[bezierPath closePath];
// 3.外边
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, 0)];
[bezierPath closePath];
//4.设置颜色
[fillColor setFill];
[bezierPath fill];
CGContextDrawPath(contextRef, kCGPathStroke);
UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return antiRoundedCornerImage;
}
效果如下
布局如下图所示
总结:这种方法是通过上面添加一层裁剪成圆形图片蒙层达到
欺骗
用户的效果,这种方法不会造成离屏渲染
,性能较高。
3.2 裁剪任意角度
上面 3.1 是只能裁剪成圆角,扩展性不是很强,下面的方法可以裁剪成任意角度的图片,扩展性很强
- 实例代码如下
/**
绘制裁剪圆角后图片
@param radius 圆角
@param rectSize 视图尺寸
@param fillColor 填充色
@return 图片
*/
- (UIImage *)drawAntiRoundedCornerImageWithRadius:(float)radius rectSize:(CGSize)rectSize fillColor:(UIColor *)fillColor {
UIGraphicsBeginImageContextWithOptions(rectSize, false, [UIScreen mainScreen].scale);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路径
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint hLeftUpPoint = CGPointMake(radius, 0);
CGPoint hRightUpPoint = CGPointMake(rectSize.width - radius, 0);
CGPoint hLeftDownPoint = CGPointMake(radius, rectSize.height);
CGPoint vLeftUpPoint = CGPointMake(0, radius);
CGPoint vRightDownPoint = CGPointMake(rectSize.width, rectSize.height - radius);
CGPoint centerLeftUp = CGPointMake(radius, radius);
CGPoint centerRightUp = CGPointMake(rectSize.width - radius, radius);
CGPoint centerLeftDown = CGPointMake(radius, rectSize.height - radius);
CGPoint centerRightDown = CGPointMake(rectSize.width - radius, rectSize.height - radius);
[bezierPath moveToPoint:hLeftUpPoint];
[bezierPath addLineToPoint:hRightUpPoint];
[bezierPath addArcWithCenter:centerRightUp radius:radius startAngle:(CGFloat)(M_PI * 3 / 2) endAngle:(CGFloat)(M_PI * 2) clockwise: true];
[bezierPath addLineToPoint:vRightDownPoint];
[bezierPath addArcWithCenter:centerRightDown radius: radius startAngle: 0 endAngle: (CGFloat)(M_PI / 2) clockwise: true];
[bezierPath addLineToPoint:hLeftDownPoint];
[bezierPath addArcWithCenter:centerLeftDown radius: radius startAngle: (CGFloat)(M_PI / 2) endAngle: (CGFloat)(M_PI) clockwise: true];
[bezierPath addLineToPoint:vLeftUpPoint];
[bezierPath addArcWithCenter:centerLeftUp radius: radius startAngle: (CGFloat)(M_PI) endAngle: (CGFloat)(M_PI * 3 / 2) clockwise: true];
[bezierPath addLineToPoint:hLeftUpPoint];
[bezierPath closePath];
//If draw drection of outer path is same with inner path, final result is just outer path.
[bezierPath moveToPoint:CGPointZero];
[bezierPath addLineToPoint:CGPointMake(0, rectSize.height)];
[bezierPath addLineToPoint:CGPointMake(rectSize.width, rectSize.height)];
[bezierPath addLineToPoint:CGPointMake(rectSize.width, 0)];
[bezierPath addLineToPoint:CGPointZero];
[bezierPath closePath];
[fillColor setFill];
[bezierPath fill];
CGContextDrawPath(contextRef, kCGPathFillStroke);
UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return antiRoundedCornerImage;
}
运行效果如下: 角度值为50,填充色分别为绿色,白色时效果
运行效果如下: 角度值为100,填充色分别为绿色,白色时效果 (圆形)
总结:这种方法扩展性很强,可以裁剪任意角度,性能很高,推荐使用。
四 Quartz2D
通过Quartz2D将图形绘制出一张圆形图片来进行显示。
- 实例代码如下
- (UIImage *)circleImage {
//1.开启图片图形上下文:注意设置透明度为非透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
//2.开启图形上下文
CGContextRef ref = UIGraphicsGetCurrentContext();
//3.绘制圆形区域(此处根据宽度来设置)
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.width);
CGContextAddEllipseInRect(ref, rect);
//4.裁剪绘图区域
CGContextClip(ref);
//5.绘制图片
[self drawInRect:rect];
//6.获取图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//7.关闭图形上下文
UIGraphicsEndImageContext();
return image;
}
运行效果如下:
总结:这种方法性能也较高,只是局限性比较大,只能针对图片进行裁剪。我们一般可以将该方法添加到
UIImage
的分类中
@interface UIImage (CSCircleImage)
- (UIImage *)circleImage;
@end
五 Mask
5.1 使用图片
Mask 效果与混合图层的效果非常相似,只是使用同一个遮罩图像时,mask 与混合图层的效果是相反的,在 Demo 里使用反向内容的遮罩来实现圆角。实现 mask 效果使用 CALayer 的layer属性,在 iOS 8 以上可以使用 UIView 的maskView属性。
- (void)drawUI {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.centerY.equalTo(self.view);
make.centerX.equalTo(self.view);
}];
// 设置Mask
if (@available(iOS 8.0, *)) {
iconImgV.maskView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"RoundMask"]];
} else {
CALayer *maskLayer = [[CALayer alloc] init];
maskLayer.frame = iconImgV.bounds;
maskLayer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"RoundMask"] CGImage]);
iconImgV.layer.mask = maskLayer;
}
}
运行效果如下:
备注:其中icon_girl.png
和RoundMask
分别为下面两张图片
如果所有 maskImage 相同的话,使用一个 maskImage 就够了,不然每次生成一个新的 UIImage 也会是一个性能隐患点。注意:可以使用同一个 maskImage,但不能使用同一个 maskView,不然同时只会有一个 mask 效果。
5.2 使用 CAShapeLayer 来指定混合的路径。
使用 mask 来实现圆角时也可以不用图片,而使用 CAShapeLayer 来指定混合的路径。
UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:iconImgV.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(40, 40)];
CAShapeLayer *shapLayer = [[CAShapeLayer alloc] init];
shapLayer.path = roundedRectPath.CGPath;
iconImgV.layer.mask = shapLayer;
运行效果如下:
我们通过设置
cornerRadii
值来设置角度值,如果为圆角,则为图片尺寸宽高的一半即可。
使用 Mask 裁剪圆角会造成离屏渲染,掉帧,卡顿。
本文参考
iOS离屏渲染优化
该文章为作者原创,如果转载,请注明出处