序文
同社は最近UIリストを開発し、開発してからセルのスタイルが実現したのですが、オフスクリーンレンダリングができるといつも思っていたので、そう感じたらチェックしたいと思いました。オフスクリーンレンダリングはありましたか?レンダリング、それをどのように引き起こすことができますか、そして良い解決策は何ですか?
最初にデザインを見てみましょう。
角が丸い白い背景。その上に画像があり、画像の左隅と右上隅も丸みを帯びており、境界線や影はありません。
どのように書きますか
CALayerとUIViewの違い
- CALayer:から継承され 、画像のレンダリングを担当し、 フレームワーク
NSObject
に属し ます。QuartzCore
- UIView: フレームワーク
UIResponder
に基づいて、主にイベント応答を担当する から継承されUIKit
ます。
両者が分かれて協力していることは明らかです。多くの人は、UIViewにも背景、フレーム、バインドなどがあると考えるでしょう。これらのプロパティの本質は、CALayerのカプセル化でもあります。
ClipsToBoundsとmasksToboundsの違い
-
ClipsToBounds:はクラスViewのプロパティです。yesに設定すると、親ビューを超える部分は表示されません。
-
masksToBounds:はクラスCALayerのプロパティです。yesに設定すると、親のViewレイヤーを超える部分は表示されません。
それらはすべて同じ効果を達成し、本質は同じであると言えます。
オフスクリーンレンダリング
オフスクリーンレンダリングとは
通常、ディスプレイにコンテンツを表示する場合は、画面と同じ量のピクセルデータFramebuffer
を。GPUはレンダリングされたコンテンツをFramebuffer
ニードルバッファーに入れ続け、ディスプレイはFramebuffer
それを続けて表示します。 display.content 。
いくつかの制限により、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
不设置背景颜色,能解决我在前言提出的问题,没想到还是离屏渲染了。
结论
-
丸みを帯びた角とトリミングを設定した後、ビューにコンテンツ(画像、描画コンテンツ、画像情報を含むサブビュー)に加えて、背景色または境界線が必要な場合、オフスクリーンレンダリングが発生します。
-
丸みを帯びた角とトリミングを設定し、さらにサブビューをトリミング領域に配置すると、画面外にもレンダリングされます。
-
丸みを帯びた角とトリミングのみで、コンテンツは画面外に表示されません。古典的な例は
【button setImage】
、一致する必要があるだけで、画面外にレンダリングされることはありませんbutton.imageView.layer.cornerRadius
。button.imageView.clipsToBounds
解決
-
デザイナーをお探しですか?角が丸くて影のあるカードなど、デザイナーにうまくデザインさせましょう。
-
UIBezierPath + CAShapeLayer?左上と右上を自分で描きますか?効果はまだオフスクリーンレンダリングであり、パフォーマンスが向上する可能性があります。
-
マスクを入手し、丸みを帯びた角をシミュレートしますか?
皆さん、序文の問題のために、オフスクリーンレンダリングなしでレンダリングすることはまだ不可能です。オンラインヘルプが大物に与えられます、ありがとう!!!