前言
公司最近开发一个UI列表,其中一个cell的样式,我开发后效果是实现了,但总是感觉会产生离屏渲染,一旦有这种感觉,我心里就想查过明白,到底有没有离屏渲染,怎么会造成的呢,有什么好的解决方案呢
我们先看下设计图先。
一个白色背景,有圆角,上面是一张图片,图片的左手角和右上角也是圆角,无边框,无阴影。
你们会怎么写呢
CALayer和UIView的区别
- CALayer:继承自
NSObject
, 负责图像渲染,属于QuartzCore
框架。 - UIView:继承自
UIResponder
, 主要负责事件响应,属于基于UIKit
框架。
很明显双方是分工合作的,很多人会想,UIView也有background,frame,bound等,这些属性本质也是对CALayer的封装而已。
clipsToBounds和masksTobounds区别
-
clipsToBounds:是类View的属性,如果设置为yes,则不显示超出父View的部分。
-
masksToBounds:是类CALayer的属性,如果设置为yes,则不显示超出父View layer的部分。
可以说他们都是达到一样的效果,本质也是一样的。
离屏渲染
什么是离屏渲染
正常我们要再显示器上显示内容,我们就需要一个和屏幕像素数据量一样的 Framebuffer
作为像素数据存储区域,GPU不停的将渲染完成后的内容放到Framebuffer
针缓冲区内,而显示器就不断在Framebuffer
上获取内容。
因为一些限制,GPU无法把渲染结果写入到Framebuffer
上,需要在当前屏幕缓冲区以外新开辟一个缓冲区Offscreen Buffer
进行渲染操作,把提前渲染好的内容放到Offscreen Buffer
上,等需要用到再写入到Framebuffer
上,这就是离屏渲染。
画家算法
画家算法是图层由远到近,一层一层绘制的。同样的道理也会应用到我们GPU渲染图层,也是一层一层往画布上输出,上层的sublayer会覆盖下层的sublayer,相当于按次序输出到framebuffer
,然后按照次序绘制到屏幕,当绘制完一层,就会将该层从帧缓存区中移除,从而节约空间提交效率。
但是有些场景,GPU一层一层渲染,渲染完成后放到framebuffer
,如果说你想改变在这一层的layer像素数据,这时候已经不能改变了,因为它在渲染中被永久覆盖了。那怎么办?
那只能令开辟一块内存作为临时中转区来完成复杂的修改/裁剪等操作,然后再切换到framebuffer
上。
离屏渲染优缺点
优点
- 处于效率目的,对于多次出现屏幕的视图,可以将内容提前渲染保存在
Offscreen Buffer
中,达到复用的目的。
缺点
-
离屏渲染需要额外开辟一个新的缓存区,这会增加存储空间,大量的离屏渲染可能会造成内存过大的压力,空间大小为屏幕总像素的2.5倍。超出就无法使用。
-
在渲染过程,我们需要进行2次上下文切换,先切换到屏幕外环境进行渲染,然后再切换到当前屏幕是很消耗性能的,而且如果最终存入到
framebuffer
上,超过16.67ms,会产生掉帧现象,造成卡顿。
离屏渲染场景
-
光栅化,
shouldRasterize
,一旦设置为true,就会把layer的渲染结果包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。主旨在于降低性能损失,但总是至少会触发一次离屏渲染。 -
mask,遮罩作用。
-
opacity,设置layer的透明度不为1。
-
shadow,投影作用。
-
绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。
-
UIBlurEffectView,高斯模糊,系统自动触发,用于保存中间状态。
-
masksToBounds,裁剪的一些情况。
圆角 + 裁剪
看了那么多概念,那我们就来分析一下关于圆角+裁剪的情况。先给一个结论:并不是所有圆角+裁剪都会产生离屏渲染。
我们先打开debug
-> Color Off-screen Rendered
,如果有黄色区域显示,就证明是产生了离屏渲染。
我们先看一下官方给的圆角离屏渲染的图片。
也就是说圆角裁剪的时候,backgroundColor
, contents
, borderWidth
, borderColor
,如果都设置了这些属性就会产生离屏渲染。
圆角+裁剪+边框+背景颜色
- (void)drawView
{
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
// 设置背景色
view1.backgroundColor = UIColor.systemPinkColor;
// 设置边框宽度和颜色
view1.layer.borderWidth = 2.0;
view1.layer.borderColor = UIColor.blackColor.CGColor;
// 设置圆角
view1.layer.cornerRadius = 100.0;
// 设置裁剪
view1.clipsToBounds = YES;
view1.center = self.view.center;
[self.view addSubview:view1];
}
复制代码
结果是没有离屏渲染。这是很明显的,因为没有需要裁剪处理的内容,所以masksToBounds
设置为YES
或者NO
都没有影响。
圆角+裁剪+contents+边框或背景色
- (void)drawView2 {
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
// 设置背景色
view1.backgroundColor = UIColor.grayColor;
// 设置边框宽度和颜色
view1.layer.borderWidth = 2.0;
view1.layer.borderColor = UIColor.blackColor.CGColor;
//设置图片
view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;
// 设置圆角
view1.layer.cornerRadius = 100.0;
// 设置裁剪
view1.clipsToBounds = YES;
view1.center = self.view.center;
[self.view addSubview:view1];
}
复制代码
从效果图上看是离屏渲染了,全部都占有了,还不有问题对吧。
圆角+裁剪+contents
- (void)drawView3 {
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
//设置图片
view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;
// 设置圆角
view1.layer.cornerRadius = 100.0;
// 设置裁剪
view1.layer.masksToBounds = YES;
view1.center = self.view.center;
[self.view addSubview:view1];
}
复制代码
从效果图上看,是没有离屏渲染的,这里是重点,我们只设置了contents,没有设置背景颜色或者边框,就不会产生离屏渲染。因为我们就只有这一层,我们单层情况下,直接裁剪就行,不用放到Offscreen Buffer
上进行渲染。
圆角+裁剪+有内容子视图+背景色或边框
- (void)drawView4 {
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
// 设置背景色
view1.backgroundColor = UIColor.grayColor;
// 设置圆角
view1.layer.cornerRadius = 100;
// 设置裁剪
view1.layer.masksToBounds = YES;
// 子视图
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100.0, 100.0)];
// 设置背景色
view2.backgroundColor = [UIColor blueColor];
[view1 addSubview:view2];
view1.center = self.view.center;
[self.view addSubview:view1];
}
复制代码
从效果图上是有离屏渲染的,子视图不在裁剪的范围的,但设了背景色就产生了离屏渲染。如果去掉view1的背景色,那就不会离屏渲染了。
圆角+裁剪+子视图在裁剪区域
- (void)drawView5 {
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 375.0, 375.0)];
// 设置圆角
view1.layer.cornerRadius = 16;
// 设置裁剪
view1.layer.masksToBounds = YES;
UIImageView * imageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"destination_head_bg"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
imageView.frame = CGRectMake(0, 0, 375.0, 200.0);
[view1 addSubview:imageView];
view1.center = self.view.center;
[self.view addSubview:view1];
}
复制代码
从效果图上看还是离屏渲染了,本来以为对view1
不设置背景颜色,能解决我在前言提出的问题,没想到还是离屏渲染了。
结论
-
一旦设置圆角+裁剪,如果视图一定是有contents(图片、绘制内容、有图像信息的子视图),加上背景色或者边框,就会产生离屏渲染。
-
设置圆角+裁剪,加上子视图位于裁剪区域,也会离屏渲染。
-
仅有圆角+裁剪,和contents是不会离屏渲染的。经典例子就是
【button setImage】
的了,只需要对button.imageView.layer.cornerRadius
和button.imageView.clipsToBounds
进行就不会离屏渲染。
解决方案
-
找设计师?让设计师都设计好,例如圆角+阴影的卡片。
-
UIBezierPath + CAShapeLayer?自己来画左上角和右上角?效果还是离屏渲染,性能可能会更好吧。
-
弄遮罩,模拟圆角?
大佬们,对于前言的问题,还是不能做出不离屏渲染的情况,在线救助给为大佬,谢谢!!!