摘要:本文为CALayer Animation系列第三篇。时至今日,无论iPhone机身样式如何改变,屏幕尺寸如何改变,iOS系统如何改变,唯有锁屏底部闪烁的滑动来解锁不变。它的动画效果是如何实现的呢?这篇文章会告诉你们答案。
在很多人的心目中iPhone的经典机型应该是iPhone4,精湛的工艺、完美的屏幕配上暗色的锁屏壁纸,看着屏幕底部闪烁的slide to unlock字样,高逼格彰显无遗。时至今日无论iPhone机身样式如何改变,屏幕尺寸如何改变,iOS系统如何改变,唯有锁屏底部闪烁的滑动来解锁不变。它的动画效果是如何实现的呢,这篇文章会告诉你们答案。
CAGradientLayer
新建一个应用名为GradientAnimation,打开Main.storyboard,将ViewController的View背景色设置为灰黑色,拖一个UIView到ViewController中,将其颜色设置为无色并设置好布局约束:
接着我们拖一个UILabel到刚才拖入的UIView中,设置高宽等同于它的父视图:
我对该UILabel的属性设置如下:
之后,我们在ViewController中添加UIView和UILabel的Outlet:
接下来回到ViewController.swift,我们添加一个常量属性gradientLayer:
let gradientLayer = CAGradientLayer() |
这里出现了CALayer的另一个子类CAGradientLayer,这个类的作用就是能在Layer上绘制出渐变颜色的效果,然后在viewDidLoad()中添加如下代码:
gradientLayer.bounds = CGRect(x: 0, y: 0, width: backgroundView.frame.size.width, height: backgroundView.frame.size.height) gradientLayer.position = CGPoint(x: backgroundView.frame.size.width/2, y: backgroundView.frame.size.height/2) |
上述两行的代码是设置Layer的大小及位置,这在上两篇文章中已经讲过,这里就不再累赘了。接着我们继续添加两行代码:
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5) gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) |
既然CAGradientLayer可以绘制出渐变颜色的效果,那自然有颜色渐变的方向,所以这两行代码的作用就是设置颜色渐变的起始点和结束点,这两个属性共同决定了颜色渐变的方向:
从上面的示意图中可以看出,CAGradientLayer是通过起始点和结束点的坐标位置来决定颜色渐变的方向的,起始点的默认值是(0.5, 0),结束点的默认值是(0.5, 1),也就是说默认的颜色渐变方向是沿垂直中线从上往下渐变的,我们在这里将它改成了沿水平中线从左往右渐变。
接下来我们设置CAGradientLayer的渐变颜色,接着添加如下代码:
gradientLayer.colors = [ UIColor.blackColor().CGColor, UIColor.whiteColor().CGColor, UIColor.blackColor().CGColor ] |
CAGradientLayer的colors属性类型是一个数组[AnyObject],这就意味着我们可以实现多个颜色的渐变效果,并且可以规定各个颜色的顺序。不过在我们这个示例中我们只需要两种颜色,不过需要注意的是虽然颜色只有两种,但是整个颜色渐变的过程中有三个原色点,那就是黑、白、黑,所以我们在这个数组中也需要按照原色点的数量和顺序添加相应的颜色,哪怕颜色都是一样的。
我们既然设置了渐变的三个原色,那么就要对这原色出现的位置进行设置,接着添加如下代码:
gradientLayer.locations = [0.2, 0.5, 0.8] |
从上述代码中不难看出,我们将第一个黑色原色出现的位置设置在了整个Layer长度的十分之二的位置,第二个白色原色在中间,第三个黑色原色在十分之八的位置:
设置完CAGradientLayer的相关属性后,我们将gradientLayer添加到backgroundView的Layer中:
backgroundView.layer.addSublayer(gradientLayer) |
现在我们编译运行一下代码看看效果:
接下来我们需要让颜色渐变动起来,先创建一个方法gradinetAnimate(),在方法中添加如下代码:
let gradient = CABasicAnimation(keyPath: "locations") gradient.fromValue = [0, 0, 0.25] gradient.toValue = [0.75, 1, 1] gradient.duration = 2.5 gradient.repeatCount = HUGE gradientLayer.addAnimation(gradient, forKey: nil) |
首先,创建了一个locations类型的动画实例gradient,将fromValue属性,也就是起始位置的属性设置为[0, 0, 0.25],它的意思是动画开始前,黑色、白色这两个原色的位置在整个Layer的最前端,第二个黑色原色在0.25的位置:
而结束位置toValue,将白色和第二个黑色原色位置设置在整个Layer的末端,第一个黑色原色在0.75的位置:
从图中可以看出,此时整个Layer都变成了黑色。也就是说,在整个动画中,第一个黑色原色从0移动到0.75的位置,白色原色从0移动到1的位置,第二个黑色原色从0.25移动到1的位置。然后设置动画时间为2.5秒,无线重复次数,最后将gradient动画添加到gradientLayer中。我们在viewDidAppear()方法中调用该动画方法gradientAnimate(),编译运行看看效果:
动画效果还不赖,但是如何将颜色渐变的动画作用在UILabel的文字上呢?其实非常简单,就是让UILabel上的文字称为CAGradientLayer的遮罩即可,我们先在ViewController中定义一个常量:
然后在viewDidAppear()中的gradientAnimate()方法之前添加如下代码:
textLabel.text = text gradientLayer.mask = textLabel.layer |
我们再编译运行代码看看效果:
到目前为止,锁屏中滑动来解锁的动画效果就完成了,这个动画效果在Facebook的Paper应用中也有使用。下一节,我们在该动画的基础上对文字再加点小动画。
Text Animation
首先我们打开AppDelegate.swift,在import UIKit下面添加一个方法:
func delay(seconds seconds: Double, completion:()->()) { let intervalTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds )) dispatch_after(intervalTime, dispatch_get_main_queue(), { completion() }) } |
这个方法的作用如其名称一样,是一个延迟方法,该方法的第一个参数是想要延迟的时间,第二个参数是一个闭包,也就是延迟的主体。这个方法用到了GCD的知识,dispatch_time主要是用于创建一个类型为dispatch_time_t的相对时间,它的第一个参数指的是起始时间,一般都是用预定义的DISPATCH_TIME_NOW作为第一个参数的值,代表当前的时间。第二个参数代表时间间隔,注意这个参数需要的时间单位是纳秒,所以我们使用预定义的NSEC_PER_SEC纳秒单位乘以希望间隔的秒数。
dispatch_after用于在队列中定时执行任务,当你想在一段时间后执行一个任务,那么就可以用这个函数。该函数的第一个参数指定延迟的时间,第二个参数指定一个队列,用于添加任务,第三个参数是一个闭包,也就是要执行的任务。
然后回到ViewController.swift,添加如下方法:
func textAnimate(text: String) { if text.characters.count > 0 { textLabel.text = "\(textLabel.text!)\(text.substringToIndex(text.startIndex.successor()))" delay(seconds: 0.4, completion: { self.textAnimate(text.substringFromIndex(text.startIndex.successor())) }) } } |
该方法的参数text就是UILabel中要显示的文字内容。substringToIndex(_ to: Int)方法的作用是从字符串的开头一直截取到指定的位置,但不包括该指定位置的字符。text.startIndex.successor()这句意思是从text的起始位置开始取后面的一个字符。substringFromIndex(_ from: Int)方法的作用是以指定位置并包括指定位置的字符开始,一直截取之后的全部字符。所以整个方法的作用是每隔0.4秒显示一个字符,直到将整个字符串显示完。最后在viewDidAppear()中注释掉textLabel.text = text这行代码,并在方法最后调用textAnimate(text)方法。编译运行代码看看最终效果:
|