iOS开发-添加圆角效果高效实现
圆角(RounderCorner)是一种很常见的视图效果,相比于直角,它更加柔和优美,易于接受。但很多人并不清楚如何设置圆角的正确方式和原理。设置圆角会带来一定的性能损耗,如何提高性能是另一个需要重点讨论的话题。我查阅了一些现有的资料,收获良多的同时也发现了一些误导人错误。本文总结整理了一些知识点,概括如下:
- 设置圆角的正确姿势及其原理
- 设置圆角的性能损耗
- 其他设置圆角的方法,以及最优选择
我为本文制作了一个 demo,读者可以在我的 github 上 clone 下来:CornerRadius,如果觉得有帮助还望给个star以示支持。项目由 Swift 实现,但请务必相信我即使你只会 Objective-C,也可以看懂它。因为其中的关键知识与 Swift 无关。
正确姿势
首先,我想要声明的一点是:
设置圆角很简单,它不会带来任何性能损耗
因为这件事本来就很简单,它只需要一行代码:
view.layer.cornerRadius = 5
先别急着关掉网页,也别急着回复,我们让事实说话。打开 Instuments,选择 Core Animation 调试,你会发现既没有 Off-Screen Render,也没有降低帧数。关于使用 Instuments 分析应用,你可以参考我的这篇文章:UIKit性能调优实战讲解。从截图中可以看到第三个棕色视图确确实实设置了圆角:
不过查看一下代码可以发现,有一个 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
这样内部还有子视图的控件就无能为力了。所以很多情况下我们会看到这样的代码:
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
我们把第二行代码添加到 CustomTableViewCell
的构造方法中,再次运行 Instument,就可以看到圆角效果了。
性能损耗
如果你勾选上 Color Offscreen-Rendered Yellow,就会发现 label 的四周出现了黄色的标记,说明这里出现了离屏渲染。关于离屏渲染的介绍,同样可以参考:UIKit性能调优实战讲解,就不在本文赘述了。
需要强调的一点是,离屏渲染并非由设置圆角导致的!通过控制变量的方法很容易得出这个结论,因为 UIView 只是设置了
,但它没有出现离屏渲染。某些比较权威的文章,比如 Stackoverflow 和 CodeReview 都提到设置
cornerRadiuscornerRadius
会导致离屏渲染从而影响性能,我想这实在是冤枉了可爱的 cornerRadius
变量,也误导了别人。
虽然设置 masksToBounds
会导致离屏渲染,从而影响性能,但是这个影响到底会有多大?在我的 iPhone6 上,即使出现了 17 个带有圆角的视图,滑动时的帧数依然在 58 - 59 fps 左右波动。
然而,这并非说明 iOS 9 做了什么特殊优化,或者是离屏渲染的影响不大,其主要原因在于圆角不够多。当我将一个
也设置成圆角,也就是屏幕上的圆角视图达到 34 个时,fps 大幅度下降,大约只有 33 左右。基本上已经达到了影响用户体验的范围。因此,一切不讲依据的优化都是耍流氓,如果你的圆角视图不多,cell 不复杂,就不要费力气折腾了。
UIImageView
高效地设置圆角
假设现在圆角视图非常多(比如在 UICollectionView 中),那么如何为视图高效的添加圆角呢?网上的教程大多没有说全,因为这个事要分两种情况考虑。为普通的
UIView
设置圆角,和为 UIImageView
设置圆角的原理截然不同。
有一种做法是这样的,这种写法试图实现 cornerRadius = 3
的效果:
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 上与屏幕等大的
,即使重写一个空的
UIViewdrawRect
方法,它也至少占用 750 * 1134 * 4 字节 ≈ 3.4 Mb
的内存。在
内存恶鬼drawRect 及其后续中,作者详细介绍了其中原理,据他测试,在 iPhone6 上空的、与屏幕等大的视图重写 drawRect
方法会消耗 5.2 Mb 内存。总之,能避免重写
drawRect
方法就尽可能避免。
其次,这种方法本质上是用遮罩层 mask
来实现,因此同样无可避免的会导致离屏渲染。我试着将此前 34 个视图的圆角改用这种方法实现,结果 fps 掉到 11 左右。已经属于卡出翔的节奏了。
忘掉这种写法吧,下面介绍正确的高效设置圆角的姿势。
为 UIView 添加圆角
这种做法的原理是手动画出圆角。虽然我们之前说过,为普通的视图直接设置 cornerRadius
属性即可。但万一不可避免的需要使用
masksToBounds
,就可以使用下面这种方法,它的核心代码如下:
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
并插入到视图层级的底部:
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)
}
}
完整的代码可以在项目中找到,使用时,你只需要这样写:
let view = UIView(frame: CGRectMake(1,2,3,4))
view.kt_addCorner(radius: 6)
为 UIImageView 添加圆角
相比于上面一种实现方法,为 UIImageView
添加圆角更为常用。它的实现思路是直接截取图片:
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 拓展一个设置圆角的方法:
extension UIImageView {
/**
/ !!!只有当 imageView 不为nil 时,调用此方法才有效果
:param: radius 圆角半径
*/
override func kt_addCorner(radius radius: CGFloat) {
self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size)
}
}
完整的代码可以在项目中找到,使用时,你只需要这样写:
let imageView = let imgView1 = UIImageView(image: UIImage(name: ""))
imageView.kt_addCorner(radius: 6)
提醒
无论使用上面哪种方法,你都需要小心使用背景颜色。因为此时我们没有设置 masksToBounds
,因此超出圆角的部分依然会被显示。因此,你不应该再使用背景颜色,可以在绘制圆角矩形时设置填充颜色来达到类似效果。
在为 UIImageView
添加圆角时,请确保 image
属性不是 nil
,否则这个设置将会无效。
实战测试
回到 demo 中,测试一下刚刚定义的这两个设置圆角的方法。首先在 setupContent
方法中把这两行代码的注释取消掉:
imgView1.kt_addCorner(radius: 5)
imgView2.kt_addCorner(radius: 5)
然后使用自定义的方法为 label 和 view 设置圆角:
view.kt_addCorner(radius: 6)
label.kt_addCorner(radius: 6)
现在,我们不仅成功的添加了圆角效果,同时还保证了性能不受影响:
总结
- 如果能够只用
cornerRadius
解决问题,就不用优化。 - 如果必须设置
masksToBounds
,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。 UIImageView
的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。
原文链接:http://www.jianshu.com/p/f970872fdc22/comments/1744724
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
iOS开发-添加圆角效果高效实现的更多相关文章
- [Xcode 实际操作]二、视图与手势-(5)给图像视图添加圆角效果
目录:[Swift]Xcode实际操作 本文将演示给矩形图片添加圆角效果 import UIKit class ViewController: UIViewController { override ...
- iOS 高效添加圆角效果实战讲解
圆角(RounderCorner)是一种很常见的视图效果,相比于直角,它更加柔和优美,易于接受.但很多人并不清楚如何设置圆角的正确方式和原理.设置圆角会带来一定的性能损耗,如何提高性能是另一个需要重点 ...
- iOS 开发之粒子效果
本文由糖炒小虾.Benna翻译 ,校对:sai.u0u0.iven.子龙山人 iOS 5中的UIKit粒子系统教程 Ray的话:这是第15篇.也是最后一篇<iOS 5 盛宴>中的iOS 5 ...
- iOS开发之抽屉效果实现
说道抽屉效果在iOS中比较有名的第三方类库就是PPRevealSideViewController.一说到第三方类库就自然而然的想到我们的CocoaPods,今天的博客中用CocoaPods引入PPR ...
- iOS UIButton添加圆角,添加边框
//准备工作 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(,, ...
- iOS开发之圆角指定
如果需要将UIView的4个角全部都为圆角,做法相当简单,只需设置其Layer的cornerRadius属性即可(项目需要使用QuartzCore框架).而若要指定某几个角(小于4)为圆角而别的不变时 ...
- iOS开发之圆角指定 分类: ios技术 2015-05-25 16:26 191人阅读 评论(0) 收藏
如果需要将UIView的4个角全部都为圆角,做法相当简单,只需设置其Layer的cornerRadius属性即可(项目需要使用QuartzCore框架).而若要指定某几个角(小于4)为圆角而别的不变时 ...
- iOS开发添加pch文件
首先说一下pch的作用: 1.存放一些全局的宏(整个项目中都用得上的宏) 2.用来包含一些全部的头文件(整个项目中都用得上的头文件) 3.能自动打开或者关闭日志输出功能 如何在Xcode中添加pch文 ...
- iOS开发添加新手引导
往往项目中经常出现此类需求 用户通过点击引导按钮可响应页面附带按钮的点击事件. // // gzhGuideView.h // GuideView // // Created by 郭志贺 on 20 ...
随机推荐
- Keras官方中文文档:keras后端Backend
所属分类:Keras Keras后端 什么是"后端" Keras是一个模型级的库,提供了快速构建深度学习网络的模块.Keras并不处理如张量乘法.卷积等底层操作.这些操作依赖于某种 ...
- [BZOJ4804]欧拉心算
题面戳我 题意:求 \[\sum_{i=1}^{n}\sum_{j=1}^{n}\phi(\gcd(i,j))\] 多组数据,\(n\le10^7\). sol SBT 单组数据\(O(\sqrt n ...
- [COGS2701]:动态树
题面 传送门 Sol LCT维护子树和 # include <bits/stdc++.h> # define IL inline # define RG register # define ...
- VS中,Ctrl+Shift+F无法在文件中查找
可能是和搜狗的繁简字切换的快捷键冲突了,把搜狗的该快捷键修改或者关闭掉即可.
- javascript 推箱子游戏介绍及问题
最近没什么事情,我的一个亲戚在学校学习PHP,课程中老师让他们编写一个javascript版本的推箱子小游戏,他没什么头绪,就来问我,我当时很闲,就随口答应他包在我身上.结果真正写的时候还是花了点时间 ...
- C++ RCSP智能指针简单实现与应用
智能指针的实现代码来源博客:<http://blog.csdn.net/to_be_better/article/details/53570910> 修改:添加 get()函数,用以获得原 ...
- js操作DOM元素
创建 document.createElement() 查找 document.getElementById() 返回对拥有指定 id 的第一个对象的引用. document.getElement ...
- python 对象和json互相转换
一.python对json的支持 从python2.6开始,python标准库中添加了对json的支持,操作json时,只需要import json即可. 二.python对象转换成json字符串 在 ...
- Mysql使用规范文档 20180223版
强制:不允许在跳板机上/生产服务器上手工连接,查询或更改线上数据 强制:所有上线脚本必须先在测试环境执行,验证通过以后方可在生产环境执行. 强制:上线脚本的编码格式统一为UTF-8 强制:访问数据库需 ...
- trigger回调方法的实现
用传参实现trigger的回调: 点击btn1触发btn2的click事件并执行trigger中传入的回调方法 <body> <input type="button&quo ...