看山不是山,再谈iOS圆角优化
设计趋势
随着移动互联网的发展,在UI设计领域,圆角和阴影逐渐成为一种不可或缺的设计手法,这离不开系统厂商对于设计风格的引导,比如:Apple产品的设计风格和UI设计规范、Google Material Design等等。不可否认,圆角设计确实会增强产品的亲和力和优雅感,但是作为开发者,我们却不那么喜欢它,因为通常大量圆角+阴影会显著降低界面滑动的流畅度。
image.png是什么造成了卡顿
有没有圆角和阴影对于CPU来说没有太大差别,额外的效果会给GPU带来较大的压力。通过Xcode提供的,View debugging可以看到,有没有圆角在渲染上的差别在于离屏渲染。离屏渲染就是图层无法直接在当前屏幕绘制,而要开辟新的缓冲区进行绘制,然后在切换到当前缓冲区进行展示,中间就包含了不少性能消耗。
离屏渲染发生与否取决于技术实现,iOS在发展过程中,也在尽量的减少离屏渲染发的发生。例如:在iOS9上设置了圆角并且maskToBounds就会触发离屏渲染,而在iOS11以后则需要在子视图也设置了圆角时才会触发。所以最好的办法就是通过Xcode提供的View debugging来查看。
优化之预合成圆角
在圆角优化方面,目前主流的技术方案是,图片下载下来后根据圆角大小来重新绘制一张切过圆角的PNG图片,然后将此图片展示到UI控件上,这样控件就不必再设置圆角。
image.png预合成圆角避免了离屏渲染,性能表现很不错,但是依然存在几点问题:
- 如果控件是Gif或者视频,预先渲染将无法实施
- 每张图片都需要重新绘制,会带来额外的CPU开销
- 原始图片和重绘后的图片都需要存入缓存,带来较大的内存占用
- 同样一张图片,如果展示的尺寸和圆角值不一样,则需要重绘多个版本
- 通常图标上会加入子视图,无法被天然裁切
换个思路,探索适用于90%场景的障眼之法
虽然我们看到的是圆角,但可能实际不是个圆角。相信有人用过这种方式生成圆角效果,简单高效,通过图层叠加,让图片看起来像是有圆角。这种方案的问题在于:不同尺寸、圆角大小、描边及阴影效果都需要提供单独的图片,通用性非常不好。
image.png抛开问题不说,这条技术路线有没有价值呢?为此统计了大多数app中的圆角设计,发现绝大多数圆角阴影效果都可以通过这种方式实现。
image.png不能实现的是背景非纯色的场景
image.png所以总体看来基于图层叠加的圆角优化技术有其独特价值。
圆角与阴影的实现
利用CoreGraphics可以绘制图片的圆角、阴影和描边,具体的不再展开。
image.pngimage.png
尺寸的自适应
关键问题来了,这个蒙层要绘制多大?
通常一套页面设计中,控件都使用一致的圆角和阴影效果,但是控件尺寸会有不同。如果每个尺寸的控件都要绘制一套蒙层,虽然技术上可以实现,但是不免让人遗憾。
经过反复求证,发现求得一个最小尺寸,来满足更大尺寸的自适应是可行的,这就解决了一张图适用多个尺寸的问题。
image.png受限于cornerRadius、offset、blur的大小,自适应蒙层有一个自适应最小值,当控件尺寸小于蒙层尺寸时则需通过另一套策略绘制,在工程实践中还没有遇到这种场景,这一块暂时没有支持。
CALayer中关于自适应的设置,contentCenter的范围是0~1.0
@property CGRect contentsCenter
SKCornerLayer支持的效果
圆角 阴影 描边 异步 缓存 DarkMode
使用方法
// 设置阴影的颜色、blur、offset、圆角支持dark mode
self.shadowView.layer.skCorner
.sShadowColor(colorDynamicFromRGBA(0x535459, 0.15, 0x0, 0))
.sShadowBlur(15)
.sShadowOffset(CGSizeMake(0, 2))
.sCornerRadiusAll(8)
.sCornerBackgroundColor(colorDynamicFromRGB(0xffffff, 0x1e1e1e));
// 异步绘制
self.shadowView.layer.skCornerLayer.displayCornerAsynchronously = YES;
性能量化
这里构了一个场景,左边使用SKCornerLayer,右边使用默认的圆角+阴影(采用了默认最优配置:圆角view没有嵌套,阴影指定shadowPath,没有产生离屏渲染)。测试中采用,通过timer以相同的速度滚动列表,以产生稳定的GPU占用率。
image.png image.png在iOS9上即使圆角后的view没有嵌套,也会产生离屏渲染,GPU开销会更大
测试设备iPhone 6sPlus iOS12.2
这里设置了两个对比组,blur分别为15和5,分别可以提升GPU性能约40%和28%,总体来看blur值越大优化效果越好。
image.png关于圆角和阴影的一些建议
- 父视图有圆角时避免子视图包含圆角效果,解决办法是,视图层级扁平化
- 对性能要求不高的场景下,通过指定shadowPath来生成阴影也是个不错的选择
- 对性能有较高要求的场景下,建议使用SKCornerLayer或者切图来构建阴影
- 最后使用View debugging检查视图