UIView
继承自UIResponder
,主要负责事件传递、事件响应,属于基于UIKit
框架
CALayer
继承自NSObject
,负责图像渲染,动画和视图的显示,属于QuartzCore
框架
而且这两大内容都符合单一职责原则,单一职责通俗地讲就是一个类只做一件事。
虽然CALayer
没有事件响应的能力,但是我们可以通过
两个方法来判断事件是不是在layer
上,从而来给事件添加点击事件
UIView
。它真正的绘图部分是由CALayer
的类来管理的。UIView
本身更像是一个CALayer
的管理器,访问其跟绘图和坐标有关的属性,例如frame
和bounds
等等,实质上内部都是在访问它所包含的CALayer
的相关属性UIView
有个layer
属性,可以返回它的主CALayer
实例。UIView
有一个layerClass
方法,返回主layer
所使用的类(默认返回就是[CALayer class]
),UIView
的子类可以通过重载这个方法来时UIView
使用不同的CALayer
来显示UIView
的CALayer
类似于UIView
的子view
树状结构,也可以向它的layer
上添加子layer
,来完成某些特殊的表示 CALayer *grayCover = [[CALayer alloc] init];
grayCover.backgroundColor = [[UIColor greenColor] CGColor];
grayCover.position = CGPointMake(200,200);
grayCover.bounds = CGRectMake(0,0,80,80);
[self.view.layer addSublayer:grayCover];
可以看到会在目标view
上对应位置将白色改为了绿色
并没有添加新图层,而是在原本view
上改变颜色,这和addsubview
并不一样。
CALayer
视图结构类似UIView
的子view
树形结构,可以向它的layer
上添加子layer
,类似于向View
上添加View
,来完成某些特殊的表示,但是不同之处就是CALayer
添加的时候并不会添加新图层,类似于修改原来的layer
。UIVIew
的layer
树形在系统内部,被系统维护三份copy
layer
的属性(比如frame\bounds
)就在这一份进行操作这三棵树的逻辑结构都是一样的,区别只是有各自的属性
tableView
对于iOS
开发者来说是无人不知无人不晓
下面我们来复习一下tableView
需要完成的操作
tableView
,tableView
需要遵循两个delegate
,, UITableViewDataSource>
,顾名思义,dataSource
意思为数据源,delegate
意为代理,其内部包含很多方法
UITableView
需要一个数据源(dataSource
)来显示数据,UITableView
会向数据源查询一共有多少行数据以及每一行显示什么数据等等。没有设置数据源的UITableView
只是个空壳。凡是遵守UITableViewDataSource
协议的OC
对象,都可以是UITableView
的数据源UITableView
设置代理对象(delegate)
,以便在UITableView
触发某些事件时做出相应的处理,比如选中了某一行。凡是遵守了UITableViewDelegate
协议的OC
对象。都可以是UITableView
的代理对象,一般会让控制器充当UITableView
的dataSource
和delegate
,通过我们手动实现协议中的某些方法来完成tableView
的实现具体到方法:
这三个方法都是dataSource
协议中的方法,我们往往通过这三个方法实现数据的显示:
//控制行数的
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
//控制组数的
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
//cell的编辑函数
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
这两个常用的方法是delegate
中的方法,提供某些交互方法:
//控制行高的
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
//cell的点击事件函数
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
tableView
,刚开始滑动时由于第一行没有完全滑出屏幕进复用池时下面新cell
已经显示到屏幕上了,所以下方这个新划出来的cell
就是新创建的cell
而不是复用的cell
(复用时也得看新加载的cell
和复用池中的备用cell
有没有同类型的id
,没有就不复用),然后接着滑动第一个cell
就完全出屏幕进入复用池了,下面我们接着滑动,接着那个需要新显示出来的cell
如果和复用池中我们保存的第一个cell
是同种id
,如果是就直接拿来复用,如果不是就新创建(判断是否能复用和新创建的操作都在cell
的编辑函数:cellForRowAtIndexPath:
中)。tableView
和下滑原理基本一致,完全出屏幕的入复用池,需要新加载到屏幕的cell
的先看池中有没有同id
的cell
,有了就复用,没有就创建。tableView
的时候,是屏幕上最多可以显示几行cell
就先创建几个cell
,此时复用池里什么都没有tableView
,刚开始滑动时由于第一行没有完全滑出屏幕进复用池时下面新cell
已经显示到屏幕上了,所以下方这个新划出来的cell
就是新创建的cell
而不是复用的cell
(复用时也得看新加载的cell
和复用池中的备用cell
有没有同类型的id,没有就不复用),然后接着滑动第一个cell就完全出屏幕进入复用池了,下面我们接着滑动,接着那个需要新显示出来的cell
如果和复用池中我们保存的第一个cell
是同种id
,如果是就直接拿来复用,如果不是就新创建(判断是否能复用和新创建的操作都在cell的编辑函数:cellForRowAtIndexPath:
中)。tableView
和下滑原理基本一致,完全出屏幕的入复用池,需要新加载到屏幕的cell
的先看池中有没有同id
的cell
,有了就复用,没有就创建。其实自定义和非自定义的cell的复用情况都是一样的
下面我们讲一个使用cell时需要注意的点:
复用时从复用池中取出来的cell
可以是已经捆绑过数据或者加过子视图的,所以,如果有必要,要清除数据,比如label
的text
)和remove
掉add
过的子视图(使用tag
),否则就有可能造成复用后出现显示的内容不符预期的情况,比如说我们有一个开始创建id为@"test"的cell,我们在执行cell
的编辑函数:cellForRowAtIndexPath:
中有一个判断分支,符合某种情况时需要在cell
上显示一个button
,而不符合那种情况的时候就不显示button
,而恰好我们这个cell
符合情况,于是就添加了button
相关的数据,然后显示出了button
,接着我们滑动了tableView
,导致我们刚才创建的这个id为@"test
"的cell
进入了复用池,然后再继续下滑的时候有一个新的id
为@"test"
的cell
需要被加载出来,于是就查看了复用池,发现里面有同id
的cell
,所以就直接拿来复用了,复用的时候对里面的数据进行了自己的设定,本类好像没什么问题,但是恰好这个新cell不符合那个要显示button
的条件,然后就不会去执行对button
添加数据的操作,我们预想着这个新cell
和之前我们创建的id
为@"test"
的cell
的区别就是新的没有button
,旧的有button,
然后两者其他控件也就是数据内容不同(比如label
的text
不一样这些),但是实际情况却是这个新创建的cell
上面显示的依然存在button
,和我们预想的不一致,其原因就是复用时所取出来的旧cell
是已经捆绑过数据且加过子视图的,虽然我们新cell
创建时没有走对button
添加数据的代码,但是由于旧cell
走过了,且向button
添加的数据都在,所以就导致新cell
上面就有一个和旧cell
一模一样的button
,这就是复用中可能存在的一个常见的难搞的问题,但是解决起来其实也非常简单,我们只需要在新cell
创建走编辑函数:cellForRowAtIndexPath:
时,在里面加一段操作,去remove
多余的那个子视图或者清除旧数据,而且依我本人之见,最好使用清除旧数据而不是remove
多余的子视图,因为这个正在新建的cell
后面也许也会进入自动释放池,而且它到时候也可能会被拿来复用,如果那个复用它的cell
刚好需要显示button
而这个被复用的cell
连button
这个视图都没添加到cell
上,那直接向button
添加数据时程序就会crash
,所以清除所有数据是不错的选择,反正每次执行编辑函数:cellForRowAtIndexPath:
时都会为对应行组的cell重新添加那些子视图上的数据(相当于覆写了旧数据),我们只需要在所有的重新添加数据操作之前讲被复用的cell
上子视图的数据全删了就行,但是如果偏要走remove
子视图的方法也不是不行,我们可以巧妙点,从复用池取出来要被复用的cell
之后直接重新alloc
初始化一下这个被复用的cell
,相当于之前清空了原本保存的所有子视图的全部数据,而原本添加到cell
上的子视图都还在,只是没有数据不显示罢了,这也是很好的手段
关于自适应高度,主要思路有两个
iOS7
之后UITableView
默认的Self-Sizing
技术,不需要实现heightForRow
方法,系统通过AutoLayout
约束自动计算cell
高度下面我们就进行一下这两个实现思路的讲解
先说第二个吧,我认为这个方法既然是新技术,那么就一定能帮我们更好的使用tableView
主要思路就是两步
AutoLayout
方法heightForRowAt
方法如何使用AutoLayout
方法?
AutoLayout
是一种自动布局技术,可以帮我们自动实现布局,苹果官方也推荐开发者尽量使用AutoLayout
来布局UI
界面。
其实就是两个核心概念,参考、约束
听起来很像我们的Masonry
的使用,确实,使用Masonry
来完成自适应高度是很舒服的,直接使用其约束条件,不用我们自己手动实现各种约束条件。
以自定义cell
中的Label
的自动展开为例
使用自适应高度的代码如下:
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ([reuseIdentifier isEqualToString:@"11"]) {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
} else {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
[self.contentView addSubview:_labelTest];
[_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
}];
}
正常使用一个Masonry
应该怎么实现呢?很简单,给top、left、size
,这三个条件就足以用最简单的方式实现一个布局操作,而自适应高度的话最后一个最接近底部的控件就得写上其距底部的高度,就是四个边,上左下右给了界限,中间的高度自己根据自己的内容的高度自己实现,达到将cell
撑开的效果。
另外,我们在用masonry
实现自适应label
展开时也得注意一个点,就是label
的numberOfLines
需要设置为0
,这个就是对label
的行数不做限制。
这基本上就是AutoLayout
的设置部分,还有一个重点的东西:[self layoutIfNeeded];
,这行代码为什么得手动写一遍?其实这涉及到了一个东西:“自动展开的Masonry
在layouSubviews
不起作用的问题”,这是因为iOS10
之前,layoutSubViews
方法在一种cell
初始化时就会调用两次,而iOS10
之后,其只会调用一次,就导致我们需要手动补上那一次,就能够让控件得到正确展开,这时候有人可能要问为什么平时没有注意到?其实是因为每种cell
初始化的时候都会调用一次,我们平时项目中一般都是多种类型的cell自定义,每种初始化时调用的layoutSubViews
方法相当于把上一种cell
所缺的一次补上了,然后自己又缺了一次,但是如果我们最后一种不是需要自适应展开的就没有问题,但如果是需要自适应展开的就会导致它无法展开,也是因为我们平时项目中最后一种cell
往往不是要展开的,所以不会注意到这个问题。
有一个小细节就是要实现cell
自适应展开时就不要去写heightForRow
方法了,否则就不会按照自适应来,关于自适应需要配置的东西各iOS
版本区别如下
而现在iOS14
以后,包括15、16
,这两个配置都不需要写了,因为rowHeight
的默认值为UITableViewAutomaticDimension
,苹果官方文档介绍如下:
翻译版如下
可以看到为什么不能自适应时去写heightForRow
方法了,里面写的清清楚楚,如果实现了heightForRow
方法,tableView
视图就会忽视自适应属性的配置。
详细的layoutSubViews
方法问题可以详见该博客:
大佬对Self-Sizing
进行了一些优化,详见该博客:iOS Self-Sizing
的一点优化
另外还有一个不用Masonry
的版本:(就是用数组存一下自己手动预设好的layout
布局,数组中包含上左下右四个位置信息的设置,然后添加到了NSLayoutConstraint
去显示,具体操作非常的秀)
下面展示一些代码实例:
本人上方写的label自适应展开的代码的各种情况如下:
如果写为(我们注释掉最后一种cell
的layoutIfNeeded
方法调用):
注: 该例子的前5
个cell
是一种,后5
个cell
是一种。
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ([reuseIdentifier isEqualToString:@"11"]) {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
} else {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
//[self layoutIfNeeded];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
[self.contentView addSubview:_labelTest];
[_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
}];
}
我们最后一种cell
并没有得到自适应展开
然后我们又修改代码为如下(恢复最后一种cell
的layoutIfNeeded
方法调用):
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ([reuseIdentifier isEqualToString:@"11"]) {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
} else {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
[self.contentView addSubview:_labelTest];
[_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
}];
}
我们的最后一种cell
也是成功自适应展开了。
接着我们又来修改一种操作(去掉layoutSubviews
方法中[super layoutSubviews]
调用父类同名方法):
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ([reuseIdentifier isEqualToString:@"11"]) {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
} else {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
[self layoutIfNeeded];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
[_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
}];
}
我们看到最后一种cell
确实是展开了,但是仔细看和上一种情况还是有一些区别的,该区别就是本次的每行cell
背后的背景色都是透明的,而上次的cell
是有默认的白色背景和分割线效果的,这也许就是layoutSubviews
方法原来默认的一些实现,不过都是小细节上的处理。
最后我们再展示一种(将label
的布局不写在layoutSubviews
方法里,而是直接写在cell
的编辑函数cellForRowAtIndexPath:
里,而且我们去掉了每种cell
初始化时调用的[self layoutIfNeeded]
):
//cell自定义的两个函数
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ([reuseIdentifier isEqualToString:@"11"]) {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
//[self layoutIfNeeded];
} else {
_labelTest = [[UILabel alloc] init];
_labelTest.numberOfLines = 0;
[self.contentView addSubview:_labelTest];
//[self layoutIfNeeded];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
}
//cell的编辑函数
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row < 5) {
TestCell *cell = [_tableViewTest dequeueReusableCellWithIdentifier:@"11" forIndexPath:indexPath];
if (cell == nil) {
cell = [[TestCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"11"];
}
cell.labelTest.text = @"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
[cell.labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(cell.labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(cell.labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(cell.labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(cell.labelTest.superview.mas_right).offset(-10.0);
}];
return cell;
} else {
TestCell *cell = [_tableViewTest dequeueReusableCellWithIdentifier:@"12" forIndexPath:indexPath];
if (cell == nil) {
cell = [[TestCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"12"];
}
cell.labelTest.text = @"222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
[cell.labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(cell.labelTest.superview.mas_top).offset(10.0);
make.bottom.equalTo(cell.labelTest.superview.mas_bottom).offset(-10);
make.left.equalTo(cell.labelTest.superview.mas_left).offset(10.0);
make.right.equalTo(cell.labelTest.superview.mas_right).offset(-10.0);
}];
return cell;
}
}
可以看到也是可以自适应展开的。
这种情况下如果不去掉[self layoutIfNeeded];
这行代码的话,其效果也是一样的,因为layoutSubviews
中已经没有关于label
布局约束的代码了,调不调没啥影响,主要是看layoutSubviews
中有没有写[super layoutSubviews]
;,写的话cell
默认就有白背景和分割线,不写的话就是一个透明背景,别的啥细节修饰都没有。
手动计算高度,进行存储
两种方法:
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10,100, 350, 0)];
label.numberOfLines = 0;
label.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
NSLog(@"the label bounds : %@",NSStringFromCGRect(label.frame));
[label sizeToFit];
NSLog(@"%f",label.frame.size.height);
[self.view addSubview:label];
这个例子我们通过设置label.numberOfLines = 0
;和[label sizeToFit]
; 就可以保证其可以自动换行且成为合适的位置,在sizeToFit
之后我们就可以得到其空间展开应有的高度。
运行效果和打印结果如下:
可以看到我们实现了label
的自适应展开,而且sizeToFit
的确成功算出了label
应有的高度。
所以我们可以用sizeToFit
方法计算出label
应有的高度,然后存入数组中,在cell
创建时在heightForRow
方法中从数组取值加载合适的cell
高度即可。
iOS 7.0
之前用sizeWithFont:
(计算的不是很准确):
CGFloat width1=[(NSString *)obj sizeWithFont:[UIFont systemFontOfSize:16] constrainedToSize:CGSizeMake(1000, FONTHEIGHT)].width;
iOS 7.0
之后用 boundingRectWithSize:
返回文本绘制所占据的矩形空间。
CGRect rect=[(NSString *)obj boundingRectWithSize:CGSizeMake(1000, FONTHEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width;
里面的参数如下:
obj
是指要计算显示的字符串boundingRectWithSize
表示计算的宽高限制CGSizeMake(1000, CGFLOAT_MAX)
1000
也可以用已经确定的控件的宽度替代self.label.width
1000
高度不限时,显示完全字符串需要的高度CGSizeMake(CGFLOAT_MAX, 200)
200
也可以用已知高度替换:self.label.height
200
时,将给定的字符串现实完全需要的宽度options
是文本绘制的附加选项,NSStringDrawingUsesLineFragmentOrigin
是默认基线attributes
字典格式,限定字符串显示的样式,一般限制字体较多,比如:@{NSFontAttributeName:[UIFont systemFontOfSize:16]}
context
包括一些信息,例如如何调整字间距以及缩放。最终,该对象包含的信息将用于文本绘制。一般写nil。
下面我们展示一个使用的例子:
UILabel * labelSecond = [[UILabel alloc] init];
labelSecond.numberOfLines = 0;
labelSecond.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
labelSecond.font = [UIFont systemFontOfSize:16];
//用我们预设的width来算label应该显示的size
CGRect rectTest = [labelSecond.text boundingRectWithSize:CGSizeMake(350, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil];
//从刚才算的size中取出heigth
//加1是怕四舍五入完之后少了一点导致显示的label少了一行
CGFloat titleHeight = ceilf(rectTest.size.height) + 1;
//用算出来的结果显示label
labelSecond.frame = CGRectMake(10,200, 350, titleHeight);
//将label作为子视图添加到父视图
[self.view addSubview:labelSecond];
可以看到实现了label
的自适应展开,与sizeToFit
同理,添加到数组中,在cell
创建时调用heightForRow
方法中从数组取值加载合适的cell
高度即可。
在使用boundingRectWithSize
计算label
高度时有几个需要注意的点,且该方法计算label
高度很容易出问题,做好一下几点就基本不会出问题了:
label
的字体大小,不能设置的17
,算的16
,这样很容易就会失去最后一行甚至多行的显示。ceilf
方法来取需要的高度或者宽度,但是由于这个过程会发生四舍五入,所以有可能差的小数部分导致最后一行的label
无法显示,所以算出来之后给结果加1,就可以避免这个问题。我们最熟悉的UIApplication
、UIView
、UIViewController
这几个类是直接继承自UIResponder
,UIResponder
类是专门用来响应用户的操作处理各种事件(UIEvent
)的。
UIResponder
提供了用户点击、按压检测(presses
)以及手势检测(motion
)的回调方法,分别对应用户开始、移动、结束以及取消,其中只有在程序强制退出或者来电时,取消事件才会调用。
On-Screen Rendering
:当前屏幕渲染,指的是 GPU
的渲染操作是在当前用于显示的屏幕缓冲区中进行
Off-Screen Rendering
:离屏渲染,分为 CPU
离屏渲染和 GPU
离屏渲染两种形式。GPU
离屏渲染指的是GPU
在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起
离屏渲染之所以会特别消耗性能,是因为要创建一个屏幕外的缓冲区,然后从当屏缓冲区切换到屏幕外的缓冲区,然后再完成渲染;其中,创建缓冲区
和切换上下文
最消耗性能,而绘制其实不是性能损耗的主要原因。
上下文之间的切换这个过程的消耗会比较昂贵,涉及到 OpenGL
的 pipeline
跟 barrier
,而且 offscreen-render
在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响。另外由于离屏渲染会增加 GPU
的工作量,可能会导致CPU+GPU
的处理时间超出 16.7ms
,导致掉帧卡顿