【翻译】圆角取舍(Corner Rounding)

当需要做圆角取舍时,许多开发者忠于使用 CALayer 的 .cornerRadius 属性。不幸的是,这个方便的属性非常消耗性能,并且只应在没有其它替代时使用。这篇文章将会包含:* 为什么你不应该使用 CALayer 的 .cornerRadius * 更多高性能的圆角取舍方式和何时使用它们 * 一张选择你理想的圆角取舍策略的流程图 * Texture 做圆角取舍的方法

June 12, 2017 -
ios texture 翻译

本文翻译自 TextureCorner Rounding 一文。

圆角取舍


当需要做圆角取舍时,许多开发者忠于使用 CALayer 的 .cornerRadius 属性。不幸的是,这个方便的属性非常消耗性能,并且只应在没有其它替代时使用。这篇文章将会包含:

  • 为什么你不应该使用 CALayer 的 .cornerRadius
  • 更多高性能的圆角取舍方式和何时使用它们
  • 一张选择你理想的圆角取舍策略的流程图
  • Texture 做圆角取舍的方法

CALayer 的 .cornerRadius 是昂贵的

为何 .cornerRadius 如此昂贵?使用 CALayer 的 .cornerRadius 属性将在每一帧(滚动期间 60 FPS)的剪裁操作上触发离屏渲染,即便那个区域的内容没有变化!这意味着在合成全部帧和每次使用 .cornerRadius 的附加路径之间,GPU 必须在每一帧上切换上下文。 重要的是,这些开销不会显示在 Time Profiler,因为它们会代表 app 影响 CoreAnimation Render Server 完成工作。这会在许多设备上剧烈消耗性能。在 iPhone 4, 4S, and 5 / 5C(连同比较 iPads / iPods),预计性能会显著下降。在 iPhone 5S 及更新设备上,即使你看不到直接影响,它将减少净空(headroom),从而造成帧落差减少。

高性能圆角取舍策略

选择一个圆角取舍策略时只需考虑3个方面:

  1. 移动发生在圆角之下吗?
  2. 移动需要经过圆角吗?
  3. 所有4个圆角都是同一个 node,并且没有其它 node 与圆角区域相交吗?

移动发生在圆角之下是指在圆角后面的任何移动。例如,作为一个在背景之上滚动的圆角 collection cell,背景将在圆角之下移动并移出。

定义移动需要经过圆角,想象一个小的圆角滚动视图包含一张大图片。在你缩放和平移滚动视图内的图片时,图片将会滚动着通过滚动视图的圆角。 Corner Rounding Movement

上面的图片使用蓝色展示移动发生在圆角之下,橙色展示移动需要经过圆角。

注:在圆角对象内部的移动,其移动行为并没有超出角落。下面图片显示的内容,绿色高亮区域,内边距等同于角落半径大小。当内容滚动时,它将不会超出角落。

Corner Rounding Scrolling

使用以上方法调整你的设计去消除角落移动的一个来源,相对于可以使用一个快速做出取舍的技巧,或者采用 .cornerRadius,能做出一些改变。 最后的考虑是确定,如果所有4个圆角都覆盖同一个 node,或者如果任何其它 node 与圆角区域相交。 Corner Rounding Overlap

预合成圆角

预合成圆角指的是圆角的绘制是在 CGContext / UIGraphicsContext 内使用贝塞尔曲线剪裁内容。在这种场景下,圆角变成了图片本身的一部分—并被压进了一个单独的 CALayer。有两种预合成圆角类型。

最好的方法是使用预合成不透明圆角。这是可使用的最高效的方法,将导致零混合(虽然这比避免离屏渲染要小得多)。不幸的是,这也是灵活性最小的一种方法;如果圆角图片需要在背景之上移动,圆角之下的背景需要是一种固定颜色。这是有可能的,但让人棘手的是使用一种纹理或图片背景色制作预合成圆角—通常最好是使用预合成 alpha 角来替代。

第二个方法涉及到使用贝塞尔曲线来预合成 alpha 角([path clip])。这个方法非常灵活并且应该是最常使用的一种。这确实会导致 alpha 混合在内容的全部大小上的开销,并将包含一个 alpha 通道比不透明预合成增加 25% 的内存使用—但是这种开销在现代机器上非常小,并且与 .cornerRadius 引发的离屏渲染不是同一个数量级。

预合成圆角的一个局限是各个圆角必须在同一个 node 上,并且不与任何子 node 相交。如果任何一个条件存在,则必须使用剪切圆角。

注意,Texture 的 node 有一项针对 .cornerRadius 的特别优化,只有在使用 .shouldRasterizeDescendants 时才会自动去预合成圆角。重要的是在你启用光栅化时要仔细考虑,不要在没有全部读过相关概念时使用这一选项。

如果你在寻找一种简单的,扁平颜色的圆角矩形或圆形,Texture 提供了一种便利去实现它。查看“UIImage+ASConveniences.h”里创建扁平颜色的方法,使用预合成圆角(支持 alpha 和不透明两种方式)创建可调整圆角尺寸的图片。这些非常适合用作 image node 的占位符或 ASButtonNode 的背景。

剪切圆角

这个策略涉及到放置4个独立的不透明圆角在内容需要做圆角取舍的顶点上。这个方法灵活,并且性能够好。它让 CPU 只需处理4个图层,每个角落一个图层的很小开销。 Clip Corners

剪切圆角适用于圆角取舍解决方案的两种情形:

  • 圆角所处的情形是圆角与多个 node 有关或与子 node 相交。
  • 圆角在静止的纹理或背景图片之上。图片剪切圆角的方法棘手,但有效!

是否可以使用 CALayer 的 .cornerRadius 属性?

只有非常少的案例适合使用 .cornerRadius。这包括动态内容移动时同时通过圆角的内部和圆角之下。对于某些动画,这是不可避免的。不管怎样,在大多数案例中,都很容易调整你的设计去消除移动的某一个来源。关于圆角移动的部分讨论了这一情况。

在屏幕上没有任何视图移动时,使用 .cornerRadius 是一个不那么糟,还可使用的捷径。不论怎样,在屏幕上有任何移动时,即使移动没有涉及到圆角,都将因为 .cornerRadius 而交性能税。例如,在一个滚动视图居其下的导航栏上有一个圆形元素,即使没有重叠也会造成影响。屏幕上运行的任何动画,即使用户没有互动,也会。另外,任何类型的屏幕刷新都会招致绘制圆角取舍的开销。

光栅化和图层背景

有人建议使用 CALayer 的 .shouldRasterize 能提高 .cornerRadius 属性的性能。这是一个(对光栅化)不太了解的选项,通常是危险的。只要没有任何情形导致它重新光栅化(没有移动,没有点击变换颜色,没有在列表视图上的移动,等等),可以使用它。通常我们不提倡这样,因为它非常容易造成更差的性能。对于没有很好的应用架构且坚持使用 CALayer 的 .cornerRadius 的人(比如说他们的应用没有非常好的性能),这能产生有意义的差异。然而,如果你在从头创建你自己的应用,我们高度推荐你选择上面所说更好的圆角取舍策略的一种。

CALayer 的 .shouldRasterize 与 Texture 的 node.shouldRasterizeDescendents 无关。当启用时,.shouldRasterizeDescendents 会阻止创建子 node 的实际视图或图层。

圆角取舍策略流程图

使用这个流程图去选择最高性能的围绕一组圆角的策略。 Corner Rounding Flowchart v2

更新

*2017-06-15 我提交的关于移除文档里对 Texture 2 的预期 Update ‘Corner Rounding’ document for Texture 2 #359 被合并进主项目了,官方文档已经更新,翻译文档也随同更新。