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 只是设置了 cornerRadius,但它没有出现离屏渲染。某些比较权威的文章,比如 Stackoverflow 和 CodeReview 都提到设置 cornerRadius 会导致离屏渲染从而影响性能,我想这实在是冤枉了可爱的 cornerRadius 变量,也误导了别人。

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

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

高效地设置圆角

假设现在圆角视图非常多(比如在 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 上与屏幕等大的 UIView,即使重写一个空的 drawRect 方法,它也至少占用 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)

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

性能测试

原文地址:http://www.jianshu.com/p/f970872fdc22

参考资料:1、小心别让圆角成了你列表的帧数杀手 http://www.cocoachina.com/ios/20150803/12873.html ()

     2、关于性能的一些问题  http://www.reviewcode.cn/article.html?reviewId=7

总结

    1. 如果能够只用 cornerRadius 解决问题,就不用优化。
    2. 如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。
    3. UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。
时间: 2024-10-08 10:03:56

iOS 高效添加圆角效果实战讲解的相关文章

iOS自定义转场动画实战讲解

iOS自定义转场动画实战讲解 转场动画这事,说简单也简单,可以通过presentViewController:animated:completion:和dismissViewControllerAnimated:completion:这一组函数以模态视图的方式展现.隐藏视图.如果用到了navigationController,还可以调用pushViewController:animated:和popViewController这一组函数将新的视图控制器压栈.弹栈. 下图中所有转场动画都是自定义的

实现IE下兼容CSS3的圆角效果

有些CSS3的牛逼的效果在IE下展示不出来是最烦人的啦,在项目中做的圆角效果到了IE下一堆方块....忒尴尬了...,找了个替代解决方案 1.首先下载一个js插件PIE.js百度一搜都是的,我也就不写出来下载地址了 2.执行的时候只需要遍历一下你要添加圆角效果的样式名就好了 $('.publicguest').each(function(){        PIE.attach(this);    }) 3.考虑到火狐下是不需要的就再添加一下判断吧 <!--[if IE]><script

iOS中如何优雅的添加圆角和边框?

因为项目需要,整理了下圆角和边框辅助类.想起前几天标哥还在微博里问圆角在tableView里卡顿的问题,想着去炫耀下.去到标哥的博客,发现已经有一定程度解决,给出开源库并且在推广,迭代了好几个版本了.. 圆角这东西被无数性能追求者津津乐道,无数小白们高山仰止. 至于圆角的几种实现方案,设置cornerRadius.加maskLayer.直接加镂空图.内存异步裁剪等等,网络上一搜一大把,这里就不再重复了.这里有两点要提醒下,纹理裁剪才是off-screen rendering的原因,而不是设置圆角

iOS给UIimage添加圆角的两种方式

众所周知,给图片添加圆角有CALayer的cornerRadius, 比如: 最直接的方法: imgView.layer.cornerRadius1=110; imgView.clipsToBounds = YES; 这事离屏渲染 (off - screen - rendering), 是很消耗性能的:有很多公司面试的时候会问到,你怎么将图片设置圆角,如果你 只回答了这个方法,那么很遗憾,没有加分. 下面我介绍一种更好的方法: #import "Bys.h" @implementati

iOS的GIF动画效果实现

引言:GIF图像格式是常见的一种动态图片格式,无论是在Web端还是在移动端都经常遇到,但是考虑目前iOS还无法原生展现GIF图片,而对于GIF的原生支持暂时也没有像JPG.PNG等图像格式支持得这么全面,因此本文从图片的合成与分解角度来为大家讲解GIF的知识,结合ImageIO框架可以更方便地实现GIF图片的合成与分解. 本文选自<iOS动画--核心技术与案例实战>. GIF在iOS中的使用场景 GIF在iOS中的使用场景有以下三个方面. (1)GIF图片分解为单帧图片. (2)一系列单帧图片

UIKit性能调优实战讲解

使用微信扫一扫查看全文干货 作者:bestswifter 在使用UIKit的过程中,性能优化是永恒的话题.很多人都看过分析优化滑动性能的文章,但其中不少文章只介绍了优化方法却对背后的原理避而不谈,或者是晦涩难懂而且读者缺乏实践体验的机会.不妨思考一下下面的问题自己是否有一个清晰的认识: 为什么要把控件尽量设置成不透明的,如果是透明的会有什么影响,如何检测这种影响? 为什么cell中的图片,尽可能要使用正确的大小.格式,如果错误会有什么影响,如何检测这种影响? 为什么设置阴影和圆角有可能影响滑动时

iOS中关于动画效果的要点

在系统并深入学习iOS动画的过程中,不得不说是个痛苦的过程.没有任何书系统的讲解这方面的知识,网上的文章都讲的支离破碎:很幸运的看到了http://objccn.io这个网站:即使如此,还是花了三天时间:这个是对整体概念模糊到不断清晰,再逐步理顺,最后总结归纳为几个关键点.我想这辈子应该都忘记不了了. iOS上的动画效果绝对赞:最常见的uitable动态效果,当手指在屏幕上下滑动时,列表会跟随其一起上下活动:如果猛的往上一推,还可以看到列表的惯性作用下,还会不断滚动,同时慢慢减速.这个过程根本不

CSS3实战开发:手把手教大家折角效果实战开发

各位网友,大家好,我是陌上花会开,今天这篇文章,我将手把手教大家如何开发一套纯CSS的折角效果.一如往常,我不提供代码下载,但是我可以保证,只要将教程中的代码复制到本地,绝对百分百获得与我演示的效果一样,希望各位明白我的用意. 好了,直接开始今天的教程吧.首先,我先给大家演示一下今天实战案例的效果: 有人会说这是什么?这就是我们今天的实战开发,我将带领大家开发上图中的右上角折叠效果. 在我讲解完之前,有些人可能觉得很难,不可思议.我想跟你们说:真的so easy.下面就请跟着我的分解步骤一步步学

[iOS]数据库第三方框架FMDB详细讲解

[iOS]数据库第三方框架FMDB详细讲解 初识FMDB iOS中原生的SQLite API在进行数据存储的时候,需要使用C语言中的函数,操作比较麻烦.于是,就出现了一系列将SQLite API进行封装的库,例如FMDB.PlausibleDatabase.sqlitepersistentobjects等. FMDB是一款简洁.易用的封装库.因此,在这里推荐使用第三方框架FMDB,它是对libsqlite3框架的封装,用起来的步骤与SQLite使用类似,并且它对于多线程的并发操作进行了处理,所以