引言
在前几篇博客中我们讨论了图层的frame,bounds,position以及让图层加载图片。但是图层事实上不仅可以显示图片,或者规则的矩形块,它还有一系列内建的特性来创建美丽优雅的页面元素。在这篇博客中我们就来探索一下CALayer的视觉效果。
视觉效果
图层的一些基础视觉效果其实在我们的日常开发过程中也经常会用到,比如圆角,边框,阴影,还有一些不常用的效果比如蒙版,下面我们就来一一讨论一下。
图层圆角
近些年圆角矩形几乎成为了主流的审美特性,不管是图标,还是页面元素,甚至文本输入框也都是按照圆角矩形来设计的。
CALayer有一个叫做cornerRadius的属性来控制着图层的圆角曲率,它是一个浮点型默认为0也就是直角。通过修改它为一个大于0的值,可以实现CALayer的圆角,默认情况下这个值只影响本图层的背景颜色,而不影响图层的背景图片或者是子图层,不过如果把maskesToBounds设置成为YES的话,图层里面的所有东西都会被截取。
未设置masksToBounds属性:
let whiteLayer = CALayer()
whiteLayer.backgroundColor = UIColor.white.cgColor
whiteLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
whiteLayer.position = self.view.center
whiteLayer.cornerRadius = 20.0
self.view.layer.addSublayer(whiteLayer)
let yellowLayer = CALayer()
yellowLayer.backgroundColor = UIColor.yellow.cgColor
yellowLayer.frame = CGRect(x: -50, y: -50, width: 100, height: 100)
whiteLayer.addSublayer(yellowLayer)
效果如下:
增加masksToBounds属性为true:
whiteLayer.masksToBounds = true
效果如下:
图层边框
CALayer的两个常用属性borderWidth和borderColor,两个属性共同决定了图层边框的样式。边框沿着图层的bounds往内绘制,同时也包含图层的圆角。
borderWidth属性定义了边框的宽度,是浮点数。
borderColor属性定义了边框的颜色默认是黑色,类型为CGColorRef。
我们使用上面的代码为图层添加边框 - 未设置masksToBounds:
let whiteLayer = CALayer()
whiteLayer.backgroundColor = UIColor.white.cgColor
whiteLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
whiteLayer.position = self.view.center
whiteLayer.cornerRadius = 20.0
whiteLayer.borderWidth = 2.0
whiteLayer.borderColor = UIColor.blue.cgColor
self.view.layer.addSublayer(whiteLayer)
let yellowLayer = CALayer()
yellowLayer.backgroundColor = UIColor.yellow.cgColor
yellowLayer.frame = CGRect(x: -50, y: -50, width: 100, height: 100)
whiteLayer.addSublayer(yellowLayer)
效果如下:
我们发现当设置边框时并不会把寄宿图或者是子图层的形状计算出来,而是沿着图层的边界进行绘制的。
图层阴影
iOS中阴影也是一个十分常见的特性,关于图层阴影的设置涉及到多个属性共同作用。
shadowOpacity:修改这个属性为一个大于0的值,阴影就可以显示在任意图层之下。它是一个介于0和1之间的浮点数。
shadowColor:控制阴影的颜色,它的类型也是CGColorRef,默认为黑色。
shadowOffset:控制阴影的方向和距离,它是一个CGSize值,宽度控制这个阴影的横向位移,高度控制阴影的纵向位移。默认值为{0,-3}向上偏移3。
shadowRadius:控制阴影的模糊程度,当设置为0的时候阴影就和图层一样有一个非常确定的边界线,当值越大边界线看上去就会越模糊和自然。
我们来创建一个橙色的图层并为它添加阴影效果,代码如下:
self.view.backgroundColor = .white
let originLayer = CALayer()
originLayer.backgroundColor = UIColor.orange.cgColor
originLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
originLayer.position = self.view.center
self.view.layer.addSublayer(originLayer)
originLayer.shadowOpacity = 0.7
originLayer.shadowOffset = CGSize(width: 0, height: 3)
originLayer.shadowRadius = 3
originLayer.shadowColor = UIColor.black.cgColor
效果如下:
shadowPath:当我们给图层设置阴影属性的时候发现还有一个属性我们没有介绍到shadowPath。
这就意味着阴影的形状我们可以随意绘制,shadowPath是一个CGPathRef类型(一个指向CGPath的指针),CGPath是一个Core Graphics对象,用来指定任意的一个矢量图形。
来修改一下上面的代码为图层添加一个圆形的阴影,代码如下:
let originLayer = CALayer()
originLayer.backgroundColor = UIColor.orange.cgColor
originLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
originLayer.position = self.view.center
self.view.layer.addSublayer(originLayer)
originLayer.shadowOpacity = 0.7
originLayer.shadowOffset = CGSize(width: 0, height: 3)
originLayer.shadowRadius = 3
originLayer.shadowColor = UIColor.black.cgColor
let circlePath = CGPath(roundedRect: CGRect(x: -25, y: -25, width: 250, height: 250), cornerWidth: 250, cornerHeight: 250, transform: nil)
originLayer.shadowPath = circlePath
效果如下:
图层的阴影另外还有两个特殊的地方,它和图层的边框不同,阴影继承自图层内容的外形,而不是图层的边界和角半径。为了计算出阴影的形状,Core Animation会将寄宿图考虑在内,包括子视图。
下面我们来加载一个图像,代码如下:
let originLayer = CALayer()
originLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
originLayer.position = self.view.center
self.view.layer.addSublayer(originLayer)
originLayer.shadowOpacity = 0.7
originLayer.shadowOffset = CGSize(width: 0, height: 3)
originLayer.shadowRadius = 3
originLayer.shadowColor = UIColor.black.cgColor
originLayer.contents = UIImage(named: "icon_dynamic_like_selected")?.cgImage
originLayer.contentsScale = UIScreen.main.scale
效果如下,Core Animation为我们创建了一个心形的阴影因为图层的内容为心形:
图层阴影的另外一个特殊的地方在于当我们设置masksToBounds的属性为true之后,超出图层部分的阴影会被裁剪掉。
这样给圆角矩形设置阴影的时候就需要花点小心思,比如使用两个相同的图层其中一个设置阴影。
图层蒙版
还有的时候我们希望展示的内容不是在一个矩形也不是圆角矩形,比如说你想显示一个星星,或者显示一个镂空的文字。这个时候我们可以使用图层蒙版来是现实。
CALayer有一个mask属性,这个属性本身就是CALayer类型,它类似一个子图层,它相对父图层进行布局,但是它却不是一个普通的子图层,mask图层定义了父图层的部分可见区域。
mask图层的color属性是无关紧要的,真正重要的是图层的轮廓。mask属性就像一个模型切割机,mask图层实心的部分也就是不透明的部分会被保留下来。
如果mask图层比父图层小,那么只有在mask图层里面的内容才是它关心的,除此之外的一切都会被隐藏起来。
下面我们来创建一个例子:
当我们设置为子图层的时候显示效果如下:
当我们设置为mask的时候代码如下:
// 背景
let bgLayer = CALayer()
bgLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
bgLayer.position = self.view.center
bgLayer.contents = UIImage(named: "random_dynamic_pic_0")?.cgImage
bgLayer.contentsScale = UIScreen.main.scale
self.view.layer.addSublayer(bgLayer)
// 蒙版
let maskLayer = CALayer()
maskLayer.frame = CGRect(x: 70, y: 70, width: 50, height: 50)
maskLayer.contents = UIImage(named: "icon_dynamic_like_selected")?.cgImage
maskLayer.contentsScale = UIScreen.main.scale
bgLayer.mask = maskLayer
效果如下:
图层的拉伸过滤方式
最后我们再来谈一下图层的minificationFilter和magnificationFilter属性,这两个属性我们不常用,他们定义了图片在被拉伸或者被压缩时采用的拉伸过滤方式。
CALayer提供了三种拉伸过滤方式:
- CALayerContentsFilter.linear
- CALayerContentsFilter.nearest
- CALayerContentsFilter.trilinear
通常来讲这两个属性的默认值都是.linear,既采用双线性滤波算法过滤器进行压缩和拉伸,大多数情况下都表现良好,双线性滤波算法通过对个像素取样来生成最终的值,会得到一个还不错的拉伸效果,但是当放大倍数比较大的时候就模糊不清了。
.trailinear和.linear非常相似,大部分情况下二者都看不出来有啥区别。相对双线性滤波算法,三线性滤波算法存储了多个大小情况下的图片,并三维取样,同时结合大图和小图的存储进而得到最后的结果。
.nearest是一种比较武断的方案,这个算法就是取样最近的单像素点,而不管其它的颜色,这样做非常快。但是最明显的效果就是会使得压缩图片更糟,放大之后也会有明显的马赛克,但是它也有适用的地方,比如对于没有斜线的小图来说最近过滤算法就要好很多。
下面举两个例子,一个带斜线的小图,图片的原始大小是50*30,我们分别使用三种算法来进行放大3倍。
代码如下:
// 原始比例
let orginLayer = CALayer()
orginLayer.frame = CGRect(x: 70, y: 100, width: 50, height: 30)
orginLayer.contents = UIImage(named: "Property 1=ktv")?.cgImage
orginLayer.contentsScale = UIScreen.main.scale
self.view.layer.addSublayer(orginLayer)
// 放大3倍 - 双线性滤波算法
let magLayer = CALayer()
magLayer.frame = CGRect(x: 70, y: 200, width: 50 * 3, height: 30 * 3)
magLayer.contents = UIImage(named: "Property 1=ktv")?.cgImage
magLayer.contentsScale = UIScreen.main.scale
magLayer.magnificationFilter = .linear
self.view.layer.addSublayer(magLayer)
// 放大3倍 - 三线性滤波算法
let magLayer1 = CALayer()
magLayer1.frame = CGRect(x: 70, y: 300, width: 50 * 3, height: 30 * 3)
magLayer1.contents = UIImage(named: "Property 1=ktv")?.cgImage
magLayer1.contentsScale = UIScreen.main.scale
magLayer1.magnificationFilter = .trilinear
self.view.layer.addSublayer(magLayer1)
// 放大3倍 - 最近邻滤波算法
let magLayer2 = CALayer()
magLayer2.frame = CGRect(x: 70, y: 400, width: 50 * 3, height: 30 * 3)
magLayer2.contents = UIImage(named: "Property 1=ktv")?.cgImage
magLayer2.contentsScale = UIScreen.main.scale
magLayer2.magnificationFilter = .nearest
self.view.layer.addSublayer(magLayer2)
效果如下:
我们来更换一张不带斜边的图片,效果如下:
总的来说呢,相对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算法会保留这种差异明显的特性以呈现更好的结果。
但是对于大多数图图尤其是有很多斜线或者曲线的图片来说,双线性和三线形滤波算法的结果更好些,而最近过滤算法会极差。
换句话说,线性过滤保留了形状,而最近过滤保留了像素差异。
总结
本篇博客介绍了一些使用代码可以实现的图层的视觉特效,比如阴影,蒙版,圆角。又介绍了一些拉伸过滤的方案。
下篇博客我们开始研究图层的变化。