CALayer
有个 mask
属性,用作 layer 的遮罩。这个遮罩和普通盖在上面的显示层不同,普通的遮罩是,盖上去,就遮住了下面的内容,而 mask
则是遮什么显示什么。
这一特性,在实现某些效果时,往往会有奇效。本文就来看看如何用 mask
结合 CAGradientLayer
做出有趣的效果。
环境信息:
Mac OS X 10.11.3
iOS 9.3
Swift 2.2
正文
先来看看常见的设置 layer
的方式:
let layer = CALayer() // 第一种:盖上去的图层(遮住就看不到下面的内容) view.layer.addSubLayer(layer) // 第二种:设置为 mask (遮住什么,下面的内容就显示出什么) view.layer.mask = layer
所以对于圆角头像的设置,除了 layer.cornerRadus
以外,还可以使用头像视图的 layer.mask
来达到效果(这里不谈他们的性能问题,仅说实现。对于在性能上的优化,建议大家看耀源的博客: iOS 保持界面流畅的技巧 ):
// 这里设置什么形状的的 path,就可以显示出什么形状的头像 let path = UIBezierPath(ovalInRect: view.bounds) let layer = CAShapeLayer() layer.frame = view.bounds layer.path = path.CGPath view.layer.mask = layer
接下来,进入到今天的正题: mask
与 CAGradientLayer
。
https://github.com/saitjr/HappyLayerFriends/tree/master/FadeMessage
这种渐隐效果,没办法用图片遮罩来代替,因为背景完全可能是个视频,多出现在播放器歌词、视频直播消息这类 app 中。不难想到的是,这里肯定有个颜色透明度的变化,但是,颜色透明度,总要有种颜色啊。白色 0 ~ 1?黑色 0 ~ 1?还是其他颜色?总不可能 clearColor
0 ~ 1 。
对于这种,要什么颜色,说不上来,但是又有渐变效果的,我首先会想到 mask
。因为它独有的遮什么显示什么的特性,分析起来实在容易:一个渐变,什么颜色无所谓,但是渐变效果是下面透明,然后越往上,透明度越高(刚好和 addSubLayer
的方式相反)。
可以看出,消息列表的渐隐,并没有影响到背景图,所以 mask
不是加在 self.view
和 imageView
上的,那么应该加在 tableView
上。
但是有个问题,加在 tableView
上,滚动时,会跟随 tableView
一起滚动, mask
不会一直停留在顶部位置。那应该加在哪个视图上面呢?来看看下面这个图:
应该是给 tableView
的 superView
加上渐变的遮罩,这样,只要是放在 tableViewSuperView
上的视图,都是上层渐隐的效果。
具体实现的代码,请见:
https://github.com/saitjr/HappyLayerFriends/tree/master/FadeMessage
为了使渐隐效果更好,需要设置 CAGradientLayer
的 locations
属性,来设置渐变的分段。
let gradientLayer = CAGradientLayer() gradientLayer.colors = [UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor, UIColor.blackColor().CGColor] gradientLayer.frame = maskView.bounds; gradientLayer.locations = [0, 0.2, 1]
https://github.com/saitjr/HappyLayerFriends/tree/master/UnlockIPhone
iOS 设备有个非常精美的滑动解锁动画。要简单的实现它,并不难,同样是 CAGradientLayer
和 mask
,玩法咋就那么多呢!
或许我们应该先看看另一种效果(暂且称为歌词动画吧,实现代码见 GitHub/TheLyrics/ ),找找思路:
橙色的滑块明显在字的下方,然后文字是镂空的。为了尽量的减少学习成本,我将前面的镂空文字,在 Sketch 中处理为了一张图(之后实现解锁效果时,我们再用代码去生成)。并不难看出,这个动画的层级:
歌词动画和我们要做的解锁动画非常相似。只不过它参与动画的是 UIView
的 frame
,而解锁动画的是 CAGradientLayer
的 locations
。 locations
属性决定了渐变的起始位置(更准确的说,应该是渐变的分段位置),所以,通过改变 locations
属性,就可以做出白色光晕在移动的效果:
让我们来看看解锁动画的视图层级:
1.初始化渐变背景
let gradientLayer = CAGradientLayer() gradientLayer.frame = containerView.bounds // 设置渐变色:黑 -> 白 -> 黑 gradientLayer.colors = [UIColor.blackColor().CGColor, UIColor.whiteColor().CGColor, UIColor.blackColor().CGColor] // locations 之后会做动画,所以这里不必在意 gradientLayer.locations = [0.25, 0.5, 0.75] // 将默认的上下渐变改为左右渐变(苹果的好像是对角线上的渐变,也可以通过这两个属性修改) gradientLayer.startPoint = CGPoint(x: 0, y: 0.5) gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) // 将渐变层放到父容器上去 containerView.layer.addSublayer(gradientLayer)
2.将文本绘制为图片
let text: NSString = "Saitjr" // 设置文本样式,这个 demo 中仅设置了居中样式 let style = NSMutableParagraphStyle() style.alignment = .Center let textAttribute: [String: AnyObject] = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Thin", size: 30)!, NSParagraphStyleAttributeName: style] // 初始画布 UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0) // 绘制文本 text.drawInRect(containerView.bounds, withAttributes: textAttribute) // 从上下文中取出 let image = UIGraphicsGetImageFromCurrentImageContext() // 关闭上下文 UIGraphicsEndImageContext()
3.合成渐变图层与文本图片
到这一步,大家可能会问,说好的 mask
呢?不是 CAGradientLayer
和 mask
的组合吗?不要急,马上就来。
因为生成的文本图片并不是镂空的,相反,文本有颜色,而图片的背景色是透明的,这就刚好用上 mask
的特性。所以,我们需要将文本图片设置为渐变层的 mask
。
let textLayer = CALayer() textLayer.frame = view.bounds.offsetBy(dx: 0, dy: 20) textLayer.contents = image.CGImage gradientLayer.mask = textLayer
4.动画
let animation = CABasicAnimation(keyPath: "locations") animation.fromValue = [0.0, 0.0, 0.25] animation.toValue = [0.75, 1.0, 1.0] animation.duration = 3.0 animation.repeatCount = Float.infinity gradientLayer.addAnimation(animation, forKey: "LocationAnimation")
完整代码见:
https://github.com/saitjr/HappyLayerFriends/tree/master/UnlockIPhone
CAGradientLayer
和 mask
的玩法还有很多,不仅如此,通过 layer 的组合,能做出很多炫酷的动画。为此我专门开了个叫 HappyLayerFriends 的 repo,希望能收集到更多有趣的动画实现,欢迎大家提供动画设计。