单独设置layer.cornerRadius = 10;
是不会触发离屏渲染的,如果再结合layer.masksToBounds = YES
,便会触发离屏渲染。少量的离屏渲染不会影响用户体验,在Core Animation 调试中,以我的iPhone6为例子,当一个界面中离屏渲染的控件超过30个的时候,FPS降到30,这个时候在滑动tableView的时候就会感觉稍微有点卡顿。
直接进入主题,避免离屏渲染我目前能想到的有两种思路
- 对控件关联的主图层进行裁剪
- 为控件添加一个遮罩(非layer.mask)
首先看第一种思路的代码
- (void)bp_setRaidus:(CGFloat)radius view:(UIView *)view {
UIImage *image = nil;
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextRef currnetContext = UIGraphicsGetCurrentContext();
[[UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:radius] addClip];
[view.layer renderInContext:currnetContext];
image = UIGraphicsGetImageFromCurrentImageContext();
view.layer.contents = (__bridge id _Nullable)(image.CGImage);
UIGraphicsEndImageContext();
}
代码的核心在于UIBezierPath 的 addClip
方法,该方法的作用是在当前上下文环境中让闭合路径区域可视化,外部区域不可视。然后再把layer
在上下文环境中渲染成一张图片,最后设置到layer.contents
中。
这种思路不会额外添加控件,性能是相对比较高,而且当控件父容器的背景颜色为非纯色的时候很好用。
但是有个问题就是每次修改控件的外观(颜色、文字、图片,尺寸)都需要重新对图层进行裁剪,赋值。所以如果想要使用这种方法,那么需要把方法抽成一个分类方法,然后利用KVO监听所有外观属性的变化,当发生改变的时候重新修改图层。
第二种思路就是为控件添加一个圆角矩形的遮罩,这个遮罩可以是UIView、也可以是CALayer,前者是控件,后者图层。这种思路是这样的,使用抑或运算
,先画一个大的矩形在上下文中,再在里面绘制一个圆角矩形,对上下文路径进行抑或就可以了,代码如下:
-(void)bp_setRaidus:(CGFloat)radius view:(UIView *)view backgroundColor:(UIColor *)color {
CALayer *layer = [CALayer layer];
layer.frame = view.bounds;
[view.layer addSublayer:layer];
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:radius];
UIBezierPath *path1 = [UIBezierPath bezierPathWithRect: layer.bounds];
[color setFill];
[path fill];
[path1 fillWithBlendMode:kCGBlendModeXOR alpha:1];
layer.contents = (__bridge id)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
}
这种做法不会像前面一样,在控件外观改变之后需要重新设置(size除外,因为修改主图层的size不会同时作用到子图层),所以不需要使用KVO对控件相关属性进行监听,使用起来方便了一些,但是为此我们添加了一个额外的图层\UIView。而且当控件所在父控件的区域的背景颜色不是纯色的时候就用不了,因为这种思路的做法是用控件父类背景颜色绘制出来的效果图。
除此之外,还有若干种可以避免离屏渲染的设置圆角方法,不过目前我能想到的那些,其核心思想都和上面两种一致,不知道各位道友有没有其它解决方案呢?
对了还可以使用Core Graphics绘图,实现CALayerDelegate的-drawLayer:inContext:
方法或者UIView中的-drawRect:
方法,在方法中使用上面两种思路实现都行。
使用Core Graphics看起来是方便了很多,其实系统帮我们做的事情和我们第一种思路差不多(外观改变会自动调用-(void)setNeedDisplay
),不过使用Core Graphics效率会比较低,而且会消耗额外的内存(虽然我们前两种思路也会消耗额外的内存,但是我们刷新的频率低呀),次数少的时候没什么,但是一旦控件的外观、内容发生改变之后,-drawRect:
就会被调用,一旦频率高了,每次都销毁对应上下文内存,再创建一份。周而复始之后,性能消耗就多了。
软件绘图(Core Graphics)的代价昂贵,除非绝对必要,你应该避免重绘你的视图。提高绘制性能的秘诀就在于尽量避免去绘制。摘自《Core-Animation-Advanced-Techniques》