文档章节

iOS 高效添加圆角效果实战讲解

 刘星石
发布于 2016/03/01 11:36
字数 2507
阅读 10
收藏 0
点赞 1
评论 0

圆角(RounderCorner)是一种很常见的视图效果,相比于直角,它更加柔和优美,易于接受。但很多人并不清楚如何设置圆角的正确方式和原理。设置圆角会带来一定的性能损耗,如何提高性能是另一个需要重点讨论的话题。我查阅了一些现有的资料,收获良多的同时也发现了一些误导人错误。本文总结整理了一些知识点,概括如下:

  • 设置圆角的正确姿势及其原理

  • 设置圆角的性能损耗

  • 其他设置圆角的方法,以及最优选择

我为本文制作了一个 demo,读者可以在我的 github 上 clone 下来:CornerRadius,如果觉得有帮助还望给个star以示支持。项目由 Swift 实现,但请务必相信我即使你只会 Objective-C,也可以看懂它。因为其中的关键知识与 Swift 无关。

我为本文制作了一个 demo,读者可以在我的 github 上 clone 下来:CornerRadius,如果觉得有帮助还望给个star以示支持。项目由 Swift 实现,但请务必相信我即使你只会 Objective-C,也可以看懂它。因为其中的关键知识与 Swift 无关。

正确姿势

首先,我想要声明的一点是:设置圆角很简单,它不会带来任何性能损耗。

因为这件事本来就很简单,它只需要一行代码:

1
view.layer.cornerRadius = 5

先别急着关掉网页,也别急着回复,我们让事实说话。打开 Instuments,选择 Core Animation 调试,你会发现既没有 Off-Screen Render,也没有降低帧数。关于使用 Instuments 分析应用,你可以参考我的这篇文章:UIKit性能调优实战讲解。从截图中可以看到第三个棕色视图确确实实设置了圆角:

1171077-ce706c8797fdcdef.jpg

不过查看一下代码可以发现,有一个 UILabel 也设置了圆角,但是没有表现出任何变化。关于这一点,你可以查看 cornerRadius 属性的注释:

By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

也就是说在默认情况下,这个属性只会影响视图的背景颜色和 border。对于 UILabel 这样内部还有子视图的控件就无能为力了。所以很多情况下我们会看到这样的代码:

1
2
label.layer.cornerRadius = 5
label.layer.masksToBounds =  true

我们把第二行代码添加到 CustomTableViewCell 的构造方法中,再次运行 Instument,就可以看到圆角效果了。

性能损耗

如果你勾选上 Color Offscreen-Rendered Yellow,就会发现 label 的四周出现了黄色的标记,说明这里出现了离屏渲染。关于离屏渲染的介绍,同样可以参考:UIKit性能调优实战讲解,就不在本文赘述了。

需要强调的一点是,离屏渲染并非由设置圆角导致的!通过控制变量的方法很容易得出这个结论,因为 UIView 只是设置了 cornerRadius,但它没有出现离屏渲染。某些比较权威的文章,比如 Stackoverflow  CodeReview 都提到设置 cornerRadius 会导致离屏渲染从而影响性能,我想这实在是冤枉了可爱的 cornerRadius 变量,也误导了别人。

虽然设置 masksToBounds 会导致离屏渲染,从而影响性能,但是这个影响到底会有多大?在我的 iPhone6 上,即使出现了 17 个带有圆角的视图,滑动时的帧数依然在 58 - 59 fps 左右波动。

然而,这并非说明 iOS 9 做了什么特殊优化,或者是离屏渲染的影响不大,其主要原因在于圆角不够多。当我将一个 UIImageView 也设置成圆角,也就是屏幕上的圆角视图达到 34 个时,fps 大幅度下降,大约只有 33 左右。基本上已经达到了影响用户体验的范围。因此,一切不讲依据的优化都是耍流氓,如果你的圆角视图不多,cell 不复杂,就不要费力气折腾了。

高效地设置圆角

假设现在圆角视图非常多(比如在 UICollectionView 中),那么如何为视图高效的添加圆角呢?网上的教程大多没有说全,因为这个事要分两种情况考虑。为普通的 UIView 设置圆角,和为 UIImageView 设置圆角的原理截然不同。

有一种做法是这样的,这种写法试图实现 cornerRadius = 3 的效果:

1
2
3
4
5
6
7
8
9
override func drawRect(rect: CGRect) {
     let maskPath = UIBezierPath(roundedRect: rect,
                                 byRoundingCorners: .AllCorners,
                                 cornerRadii: CGSize(width: 3, height: 3))
     let maskLayer = CAShapeLayer()
     maskLayer.frame = self.bounds
     maskLayer.path = maskPath.CGPath
     self.layer.mask = maskLayer
}

不过这是一种错的离谱的写法!

首先,我们应该尽量避免重写 drawRect 方法。不恰当的使用这个方法会导致内存暴增。举个例子,iPhone6 上与屏幕等大的 UIView,即使重写一个空的 drawRect 方法,它也至少占用 750 * 1134 * 4 字节 ≈ 3.4 Mb 的内存。在内存恶鬼drawRect 及其后续中,作者详细介绍了其中原理,据他测试,在 iPhone6 上空的、与屏幕等大的视图重写 drawRect 方法会消耗 5.2 Mb 内存。总之,能避免重写 drawRect 方法就尽可能避免。

其次,这种方法本质上是用遮罩层 mask 来实现,因此同样无可避免的会导致离屏渲染。我试着将此前 34 个视图的圆角改用这种方法实现,结果 fps 掉到 11 左右。已经属于卡出翔的节奏了。

忘掉这种写法吧,下面介绍正确的高效设置圆角的姿势。

为 UIView 添加圆角

这种做法的原理是手动画出圆角。虽然我们之前说过,为普通的视图直接设置 cornerRadius 属性即可。但万一不可避免的需要使用 masksToBounds,就可以使用下面这种方法,它的核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func kt_drawRectWithRoundedCorner(radius radius: CGFloat,       
                        borderWidth: CGFloat,
                                   backgroundColor: UIColor,
                                   borderColor: UIColor) -> UIImage {    
      UIGraphicsBeginImageContextWithOptions(sizeToFit,  false , UIScreen.mainScreen().scale)
      let context = UIGraphicsGetCurrentContext()
     
      CGContextMoveToPoint(context, 开始位置);   // 开始坐标右边开始
      CGContextAddArcToPoint(context, x1, y1, x2, y2, radius);   // 这种类型的代码重复四次
   
      CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)  
      let output = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      return  output
}

这个方法返回的是 UIImage,也就是说我们利用 Core Graphics 自己画出了一个圆角矩形。除了一些必要的代码外,最核心的就是 CGContextAddArcToPoint 函数。它中间的四个参数表示曲线的起点和终点坐标,最后一个参数表示半径。调用了四次函数后,就可以画出圆角矩形。最后再从当前的绘图上下文中获取图片并返回。

有了这个图片后,我们创建一个 UIImageView 并插入到视图层级的底部:

1
2
3
4
5
6
7
8
9
10
11
12
extension UIView {
     func kt_addCorner(radius radius: CGFloat,
                       borderWidth: CGFloat,
                       backgroundColor: UIColor,
                       borderColor: UIColor) {
         let imageView = UIImageView(image: kt_drawRectWithRoundedCorner(radius: radius,
                                     borderWidth: borderWidth,
                                     backgroundColor: backgroundColor,
                                     borderColor: borderColor))
         self.insertSubview(imageView, atIndex: 0)
     }
}

完整的代码可以在项目中找到,使用时,你只需要这样写:

1
2
let view = UIView(frame: CGRectMake(1,2,3,4))
view.kt_addCorner(radius: 6)

为 UIImageView 添加圆角

相比于上面一种实现方法,为 UIImageView 添加圆角更为常用。它的实现思路是直接截取图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension UIImage {
     func kt_drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage {
         let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit)
         
         UIGraphicsBeginImageContextWithOptions(rect.size,  false , UIScreen.mainScreen().scale)
         CGContextAddPath(UIGraphicsGetCurrentContext(),
             UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.AllCorners,
                 cornerRadii: CGSize(width: radius, height: radius)).CGPath)
         CGContextClip(UIGraphicsGetCurrentContext())
         
         self.drawInRect(rect)
         CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
         let output = UIGraphicsGetImageFromCurrentImageContext();
         UIGraphicsEndImageContext();
         
         return  output
     }
}

圆角路径直接用贝塞尔曲线绘制,一个意外的 bonus 是还可以选择哪几个角有圆角效果。这个函数的效果是将原来的 UIImage 剪裁出圆角。配合着这函数,我们可以为 UIImageView 拓展一个设置圆角的方法:

1
2
3
4
5
6
7
8
9
extension UIImageView {
     /**
      / !!!只有当 imageView 不为nil 时,调用此方法才有效果
      :param: radius 圆角半径
      */
     override func kt_addCorner(radius radius: CGFloat) {
         self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size)
     }
}

完整的代码可以在项目中找到,使用时,你只需要这样写:

1
2
let imageView = let imgView1 = UIImageView(image: UIImage(name:  "" ))
imageView.kt_addCorner(radius: 6)

提醒:

无论使用上面哪种方法,你都需要小心使用背景颜色。因为此时我们没有设置 masksToBounds,因此超出圆角的部分依然会被显示。因此,你不应该再使用背景颜色,可以在绘制圆角矩形时设置填充颜色来达到类似效果。

在为 UIImageView 添加圆角时,请确保 image 属性不是 nil,否则这个设置将会无效。

实战测试

回到 demo 中,测试一下刚刚定义的这两个设置圆角的方法。首先在 setupContent 方法中把这两行代码的注释取消掉:

1
2
imgView1.kt_addCorner(radius: 5)
imgView2.kt_addCorner(radius: 5)

然后使用自定义的方法为 label 和 view 设置圆角:

1
2
view.kt_addCorner(radius: 6)
label.kt_addCorner(radius: 6)

现在,我们不仅成功的添加了圆角效果,同时还保证了性能不受影响:

1171077-331ca6074d5b02c2.jpeg

性能测试

总结

  • 如果能够只用 cornerRadius 解决问题,就不用优化。

  • 如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。

  • UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。

‍问啊-一键呼叫程序员答题神器,牛人一对一服务,开发者编程必备官方网站:www.wenaaa.com

QQ群290551701 聚集很多互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!

本文转载自:http://www.cocoachina.com/ios/20160301/15486.html

共有 人打赏支持
粉丝 16
博文 142
码字总数 13945
作品 0
天津
iOS Simulator功能介绍关于Xamarin IOS开发

iOS Simulator功能介绍关于Xamarin IOS开发 iOS Simulator功能介绍 在图1.38所示的运行效果中,所见到的类似于手机的模型就是iOS Simulator。在没有iPhone或iPad设备时,可以使用iOS Simulat...

大学霸
2015/04/17
0
0
Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序

Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序 C#原本是用来编写Windows以及Windows Phone的应用程序。自从Xamarin问世后,C#的作用就发生了很大的变化。它不仅可以编写关于Windows...

大学霸
2014/10/23
0
0
微信公众平台开发:进阶篇(Web App开发入门)

WebApp与Native App有何区别呢? Native App: 1、开发成本非常大。一般使用的开发语言为JAVA、C++、Objective-C。 2、更新体验较差、同时也比较麻烦。每一次发布新的版本,都需要做版本打包...

LorinLuo
2015/03/06
0
0
Xamarin iOS教程之编辑界面编写代码

Xamarin iOS教程之编辑界面编写代码 Xamarin iOS的Interface Builder Interface Builder被称为编辑界面。它是一个虚拟的图形化设计工具,用来为iOS应用程序创建图形界面。单击MainStoryboar...

大学霸
2015/06/11
0
0
WebApp与Native App的区别

WebApp与Native App的区别 Native App: 1、开发成本非常大。一般使用的开发语言为JAVA、C++、Objective-C。 2、更新体验较差、同时也比较麻烦。每一次发布新的版本,都需要做版本打包,且需...

一真的鱼
07/03
0
0
Xamarin iOS开发实战1.1.3Xamarin版本

Xamarin iOS开发实战1.1.3Xamarin版本 Xamarin提供了免费版和付费版。免费版本包含Xamarin Studio服务。付费版本分为普通版299美元/年、商业版999美元/年和企业版1899美元/年。开发者可以根据...

大学霸
2014/12/17
0
0
iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序

iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序 程序是为了实现特定目标或解决特定问题而用计算机语言编写的命令序列的集合。本章将以编写第一个Sprite Kit程序为主线,为开发...

大学霸
2014/12/23
0
0
iOS原生混合RN开发最佳实践

做过原生iOS开发或者Android开发的同学们肯定也都了解Hybrid,有一些Hybrid的开发经验,目前我们企业开发中运用最广泛的Hybrid App技术就是原生与H5 hybrid,在早期的时候,可能部分同学也接...

光强
05/16
0
0
Xamarin iOS教程之添加和定制视图

Xamarin iOS教程之添加和定制视图 Xamarin iOS用户界面——视图 在iPhone或者iPad中,用户看到的摸到的都是视图。视图是用户界面的重要组成元素。例如,想要让用户实现文本输入时,需要使用输...

大学霸
2015/06/16
0
0
Xamarin iOS编写第一个应用程序创建工程

Xamarin iOS编写第一个应用程序创建工程 在Xcode以及Xamarin安装好后,就可以在Xamarin Studio中编写程序了。本节将主要讲解在Xamarin Studio中如何进行工程的创建以及编写代码等内容Xamarin...

大学霸
2015/01/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

RabbitMQ实战:5种模式和示例

应用RabbitMQ的5种队列 一、简单队列 P:消息的生产者 C:消息的消费者 红色:队列 生产者实现思路: 创建连接工厂ConnectionFactory,设置服务地址127.0.0.1,端口号5672,设置用户名、密码...

spinachgit
8分钟前
0
0
mysql常见报错标号对应原因以及处理方法

mysql常见报错标号以及对应解决方法 报错标号 报错现象 解决方法 原因 1449 Cause: java.sql.SQLException: The user specified as a definer ('authplat_dev'@'%') does not exist 在控制台...

ChinaHYF
10分钟前
0
0
Java 监控系统技术选型

(1)操作系统监控 Sigar oshi (2)Tomcat监控 JMX 日志 (3)Oracle监控 日志 直连SQL查询 基于Druid连接池

cccyb
11分钟前
1
0
解决IDEA中moduel配置了maven依赖可是依然不能使用依赖中的类

POM.xml中明明配置了依赖,也开启了maven的 auto-import 下面的刷新maven也没用: 直到使用下面的解决办法才使依赖生效: IDEA打开右侧 maven projects 点击顶部的M图表(看下图) 出现如下对...

颖辉小居
12分钟前
0
0
Nginx proxy pass路由转发简单用法

一,在nginx中配置proxy_pass时的加不加/的问题要注意proxy_pass后的url最后的/当加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理走如果没有/,则会把匹配的...

binhu
12分钟前
0
0
postcss替换sass-loader

实际需求: 已经使用了postcss autoprefixer ,而且sass-loader又依赖于node-sass,而且node-sass又是一堆坑(比如centos升级node6->node8后的安装权限问题),所以想简单使用Postcss 处理s...

吟啸_徐行
15分钟前
0
0
FISCO-BCOS v1.3.1 通过物料包安装记录

本文是从FISCO-BCOS的官方GitHub中的安装包进行安装的记录过程 1. Node.js环境准备 #nodejs安装 nvmsudo curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh......

undefine
19分钟前
0
0
Linux 系统日志、screen 工具

1、Linux 系统日志 日志重要吗?必须的,没有日志我们怎么知道系统状况?没有日志如何排查一个trouble?日志记录了系统每天发生的各种各样的事情,你可以通过它来检查错误发生的原因,或者受...

JolieLin
29分钟前
1
0
MAVEN打包时报错“程序包xxx不存在”

1.错误场景: 项目为springboot项目,maven聚合工程,分为app、api 、common、gongqiu四个项目,app、api 、common的父类是gongqiu,app和api依赖common,打包时报错common中的程序包xxx不存...

无语年华
30分钟前
0
0
CSS

一、简介 CSS 指层叠样式表 (Cascading Style Sheets) 样式定义如何显示 HTML 元素 样式通常存储在样式表中 把样式添加到 HTML 4.0 中,是为了解决内容与表现分离的问题 外部样式表可以极大提...

blackfoxya
38分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部