这篇文章经过两个加载动画向你们介绍CALayer的动画。按照面向对象的思想说,Layer其实就是一个模型类,像Java里的POJO类同样,它包含若干属性,并无任何处理逻辑的方法,这些属性影响着显示在Layer中的内容。咱们先来看看UIView和CALayer之间有什么区别和联系。swift
联系:Layer是View背后的那个女人。每个UIView后面都有对应的CALayer,你们看到的在UIView中显示的内容实际上是在CALayer中。数组
区别:浏览器
View有复杂的、各类组合的布局机制。Layer只有极简单的布局。app
View能够响应用户交互。Layer不能响应用户交互。框架
View中的绘画逻辑有CPU执行。Layer中的绘画直接有GPU执行。ide
View有丰富的、功能强大的子类。Layer只有不多的几个子类。布局
View动画属性较少,局限性较大。Layer因为更底层、动画属性更多,因此能够实现出更灵活、更丰富的动画。动画
Layer动画系列的文章,我不许备系统的从简单到复杂的知识进行讲解,我会经过各类实战示例,示例中用到什么知识点就讲什么知识点。spa
第一个动画让咱们来实现Google Chrome浏览器加载时页签上的等待动画:.net
新建项目GoogleLoading,打开Main.storyboard
,拖拽一个UIView到ViewController中,添加好约束,自行设置ViewController和UIView的背景色,这里UIView的背景色我设置为无色:
而后添加该View的Outlet到ViewController
中,这个UIView就是要展现加载动画的View:
[cpp] view plaincopy
@IBOutlet weak var loadingView: UIView!
打开ViewController.swift
,申明一个常量属性ovalShapleLayer
:
[cpp] view plaincopy
let ovalShapeLayer: CAShapeLayer = CAShapeLayer()
ovalShapleLayer
的类型是CAShapleLayer
,它是CALayer
的为数很少的子类之一。它的做用是在屏幕上画出各类形状,不管是简单的圆形、方形仍是复杂的五角星或不规则图形都难不住它。CAShapeLayer
有以下一些主要属性:
strokeColor:笔画颜色。
strokeStart:笔画开始位置。
strokeEnd:笔画结束位置。
fillColor:图形填充颜色。
lineWidth:笔画宽度,即笔画的粗细程度。
lineDashPattern:虚线模式。
path:图形的路径。
lineCap:笔画未闭合位置的形状。
咱们之所要申明一个CAShapeLayer
,是由于要用它在屏幕上画出一个圆形。下面在viewDidLoad()
方法中添加以下代码:
[cpp] view plaincopy
ovalShapeLayer.strokeColor = UIColor.whiteColor().CGColor
ovalShapeLayer.fillColor = UIColor.clearColor().CGColor
ovalShapeLayer.lineWidth = 7
这几个属性刚才已经向你们介绍过了,这三行代码的意思是咱们画出的圆形笔画颜色是白色,没有填充色,笔画的宽度为7。接着咱们申明这个圆形的半径,使这个圆形的大小为容纳它视图大小的80%:
[cpp] view plaincopy
let ovalRadius = loadingView.frame.size.height/2 * 0.8
最后咱们设置ovalShapeLayer
的路径,这是最关键的一步,由于你要告知CAShapeLayer
按照什么路径绘制图形,让咱们接着添加以下代码:
[cpp] view plaincopy
ovalShapeLayer.path = UIBezierPath(ovalInRect: CGRect(x: loadingView.frame.size.width/2 - ovalRadius, y: loadingView.frame.size.height/2 - ovalRadius, width: ovalRadius * 2, height: ovalRadius * 2)).CGPath
这里出现了新面孔UIBezierPath
,它能够建立基于矢量的路径,是Core Graphics框架关于path的封装。UIBezierPath
能够定义简单的形状路径,如椭圆、矩形,或者有多个直线和曲线段组成的形状。在这里咱们要使用它的初始化方法init(ovalInRect rect: CGRect)
定义一个正圆的路径。设置完路径后,将ovalShapeLayer
添加到loadingView
视图的Layer中,它就能够按照设定好的路径在loadingView
中绘制图形了:
[cpp] view plaincopy
loadingView.layer.addSublayer(ovalShapeLayer)
编译运行看看效果:
完美的一个圆形。接下来咱们要作的是让这个圆只显示一部分,由于Google的加载动画只有大概五分之二的圆形轮廓。让咱们继续将目光集中在viewDidLoad()
方法中,在loadingView.layer.addSublayer(ovalShapeLayer)
这行代码上面添加另外一行代码:
[cpp] view plaincopy
ovalShapeLayer.strokeEnd = 0.4
上面的代码将ovalShapeLayer
的strokeEnd
属性设置为0.4,意思是ovalShapeLayer
在绘制圆形时只画整个圆形的五分之二,即笔画结束的位置在整个圆形轮廓的五分之二处。编译运行看看效果:
看来是咱们想要的效果,可是仍有一处细节须要咱们完善,看看Google的加载动画,蓝色的部分圆形轮廓两头是圆形的,而咱们的圆形轮廓两头是方形的。这个问题很好解决,仍然在loadingView.layer.addSublayer(ovalShapeLayer)
这行代码上面添加一行代码:
[cpp] view plaincopy
ovalShapeLayer.lineCap = kCALineCapRound
这行代码的意思是将笔画两头的形状设置为圆形,对应的还有两个常量kCALineCapButt
,kCALineCapSquare
,你们能够试试。再次编译运行看看效果:
到目前为止,咱们经过CALayer绘制出了动画的主体,接下来要让它动起来。在ViewController.swift
中添加beginSimpleAnimate()
方法:
[cpp] view plaincopy
func beginSimpleAnimation() {
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.duration = 1.5
rotate.fromValue = 0
rotate.toValue = 2 * M_PI
rotate.repeatCount = HUGE
loadingView.layer.addAnimation(rotate, forKey: nil)
}
在这个方法中,咱们又看到了新面孔CABasicAnimation
,该类提供了基本的、单关键帧的Layer属性动画,经过animationWithKeyPath:
初始化方法,根据keyPath
建立不一样的CAPropertyAnimation
实例。经常使用的keyPath
有以下一些:
transform.rotation
:旋转动画。
transform.ratation.x
:按x轴旋转动画。
transform.ratation.y
:按y轴旋转动画。
transform.ratation.z
:按z轴旋转动画。
transform.scale
:按比例放大缩小动画。
transform.scale.x
:在x轴按比例放大缩小动画。
transform.scale.y
:在y轴按比例放大缩小动画。
transform.scale.z
:在z轴按比例放大缩小动画。
position
:移动位置动画。
opacity
:透明度动画。
以上只是一部分经常使用的动画keyPath
,更多的但愿你们在实际运用中去挖掘。在beginSimpleAnimation()
方法中,咱们使用了transform.rotation
,建立了一个旋转动画的实例,而后给该动画设置了四个属性:
duration
:动画持续时间。
fromValue
:动画起始值。
toValue
:动画结束值。
repeatCount
:重复次数。
该方法设置这几个属性的含义为使动画主体不停的旋转,旋转一圈的时间为1.5秒。以上这几个概念在UIView的动画中一样存在,你们应该都已经比较熟悉了。而后使用Layer的addAnimation(anim: CAAnimation, forKey key: String?)
方法将旋转动画实例添加到目标Layer中,该方法的key
是用来标示添加的动画,便于之后重复使用时能方便的检索,若是没有需求能够传值nil
。最后viewWillAppear
方法中调用beginSimpleAnimation()
方法:
[cpp] view plaincopy
override func viewWillAppear(animated: Bool) {
beginSimpleAnimation()
}
编译运行看看效果:
至此咱们的第一个简单的CALayer动画就完成了,在下一节咱们一块儿实现一个更加有意思的加载动画,从而向你们介绍新的动画类型及动画组合。
让咱们先看看要实现的效果:
这种加载动画在不少应用中都出现过,好比网易新闻、Win版的谷歌浏览器中都有使用。下面就让咱们一步一步来实现吧,首先打开Main.storyboard
,新添加一个UIView,在ViewController.swift
中添加Outlet:
而后定义一个新的CAShapeLayer:
[cpp] view plaincopy
let anotherOvalShapeLayer: CAShapeLayer = CAShapeLayer()
在viewDidLoad()
方法中对它进行设置,并将其添加到刚才建立的complexLoadingView
中:
[cpp] view plaincopy
anotherOvalShapeLayer.strokeColor = UIColor.whiteColor().CGColor
anotherOvalShapeLayer.fillColor = UIColor.clearColor().CGColor
anotherOvalShapeLayer.lineWidth = 7
let anotherOvalRadius = complexLoadingView.frame.size.height/2 * 0.8
anotherOvalShapeLayer.path = UIBezierPath(ovalInRect: CGRect(x: complexLoadingView.frame.size.width/2 - anotherOvalRadius, y: complexLoadingView.frame.size.height/2 - anotherOvalRadius, width: anotherOvalRadius * 2, height: anotherOvalRadius * 2)).CGPath
anotherOvalShapeLayer.lineCap = kCALineCapRound
complexLoadingView.layer.addSublayer(anotherOvalShapeLayer)
这些操做在上一个动画都已经作过一遍了,这里就再也不解释。编译运行看看是否屏幕上又出现了一个圆圈呢:
接下来在ViewController.swift
中添加一个方法beginComplexAnimation()
:
[cpp] view plaincopy
let strokeStartAnimate = CABasicAnimation(keyPath: "strokeStart")
strokeStartAnimate.fromValue = -0.5
strokeStartAnimate.toValue = 1
let strokeEndAnimate = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimate.fromValue = 0.0
strokeEndAnimate.toValue = 1
let strokeAnimateGroup = CAAnimationGroup()
strokeAnimateGroup.duration = 1.5
strokeAnimateGroup.repeatCount = HUGE
strokeAnimateGroup.animations = [strokeStartAnimate, strokeEndAnimate]
anotherOvalShapeLayer.addAnimation(strokeAnimateGroup, forKey: nil)
这里出现了两个新的动画类型,笔画开始动画和笔画结束动画,咱们虽然使用CAShapeLayer绘制了一个圆圈,可是它也存在笔画起始位置和笔画终止位置,只不过它俩在同一个位置而已,笔画动画的位置取值在0–1之间,0表明绘制路径的起始位置,1表明绘制路径的终止位置。
因此strokeStartAnimate
动画让绘制圆圈的笔画起始位置从–0.5开始,目的是让笔画起始绘制时等待一段时间,也就是起始位置延迟绘制。而strokeEndAnimate
动画让绘制圆圈的笔画终止位置正常的从0绘制到1。这样一来笔画两头绘制的时间就会不同,会有一个时间差,这样就有圆圈不断绘制又不断被擦除的效果。
strokeStartAnimate
和strokeEndAnimate
是两个动画,如何做用于一个Layer上呢?这时就要用到CAAnimationGroup
,顾名思义它是将多个动画组成一个组,在一个动画组里,子动画会同时进行。动画组能够设置动画持续时间、重复次数以及子动画数组。最后将动画组加在Layer上便可。
最后在viewWillAppear()
方法中调用beginComplexAnimation()
方法:
[cpp] view plaincopy
override func viewWillAppear(animated: Bool) {
beginSimpleAnimation()
beginComplexAnimation()
}
编译运行看看效果:
CALayer动画能够实现比UIView动画更丰富、更底层、效率更高的动画。可是在实际的应用开发中,咱们应该按需所用,能用UIView动画实现的咱们就能够不用CALayer动画,它俩没有谁优谁劣之分。这篇文章只是CALayer动画的引子,让你们对CALayer动画有初步的了解和认识,以后我在文章中会经过更多的实例帮你们更深刻的认识CALayer动画,从而提高本身应用的用户体验。