隐式动画
Core Animation基于一个假设,说屏幕上的任何东西都可以做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则它会一直存在,这其实就是所谓的隐式动画。
事务
实际上动画执行的时间取决于当前的事务设置
,动画类型取决于图层行为
。事务是通过CATransaction
类来做管理。CATransaction没有属性或者实例方法,并且也不能用+alloc和-init方法创建它。但是可以用+begin
和+commit
分别来入栈或者出栈。
任何可以做动画的图层属性都会被添加到栈顶的事务,可以通过+setAnimationDuration:
方法设置当前事务的动画时间,或者通过+animationDuration
方法来获取值(默认0.25秒)。
Core Animation在每个run loop
周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。
CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
let red:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let green:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let blue:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
self.colorLayer.backgroundColor=UIColor(red: red, green: green, blue: blue, alpha: 1.0).CGColor
CATransaction.commit()
完成块
基于UIView的block的动画允许你在动画结束的时候提供一个完成的动作。CATranscation接口提供的+setCompletionBlock:
方法也有同样的功能。
CATransaction.setCompletionBlock { () -> Void in
var transform:CGAffineTransform=self.colorLayer.affineTransform()
transform=CGAffineTransformRotate(transform,CGFloat(M_PI_2))
self.colorLayer.setAffineTransform(transform) }
图层行为
我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调用-actionForKey:
方法,传递属性的名称。其步骤为:
- 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的
-actionForLayer:forKey
方法。如果有,直接调用并返回结果。 - 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
- 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
- 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的
-defaultActionForKey:
方法。
返回nil并不是禁用隐式动画的唯一方法,CATransaction有个方法+setDisableActions:
,可以用来对所有属性打开或者关闭隐式动画。
CATransaction.setDisableActions(true)
行为通常是一个被Core Animation隐式调用的显式动画对象。这里我们使用的是一个实现了CATransaction的实例,叫做推进过渡。
let transition=CATransition()
transition.type=kCATransitionPush
transition.subtype=kCATransitionFromLeft
self.colorLayer.actions=["backgroundColor":transition]
呈现与模型
每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentationLayer
方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。
在什么情况下呈现图层会变得很有用,一个是同步动画
,一个是处理用户交互
:
- 如果你在实现一个基于
定时器
的动画,而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。 - 如果你想让你做动画的图层
响应
用户输入,你可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。
显式动画
属性动画
关键帧动画
let bezierPath=UIBezierPath()
bezierPath.moveToPoint(CGPointMake(0, 150))
bezierPath.addCurveToPoint(CGPointMake(300, 150),
controlPoint1: CGPointMake(75, 0),
controlPoint2: CGPointMake(225, 300))
let pathLayer=CAShapeLayer()
pathLayer.path=bezierPath.CGPath
pathLayer.fillColor=UIColor.clearColor().CGColor
pathLayer.strokeColor=UIColor.redColor().CGColor
pathLayer.lineWidth=3.0
self.containerView.layer.addSublayer(pathLayer)
let shipLayer=CALayer()
shipLayer.frame=CGRectMake(0, 0, 64, 64)
shipLayer.position=CGPointMake(0, 150)
shipLayer.contents=UIImage(named: "logo")!.CGImage
self.containerView.layer.addSublayer(shipLayer)
let animation=CAKeyframeAnimation()
animation.keyPath="position"
animation.duration=4.0
animation.path=bezierPath.CGPath
animation.rotationMode = kCAAnimationRotateAuto//图层将会根据曲线的切线自动旋转
shipLayer.addAnimation(animation, forKey: nil)
虚拟属性
let animation=CABasicAnimation()
animation.keyPath="transform.rotation"
animation.duration=2.0
animation.byValue=CGFloat(M_PI_2*2)
shipLayer.addAnimation(animation, forKey: nil)
使用transform.rotation而不是transform的好处是:
- 我们可以不通过关键帧一步旋转多于180度的动画;
- 可以用相对值而不是绝对值旋转(设置byValue而不是toValue);
- 可以不用创建CATransform3D,而是使用一个简单的数值来指定角度;
- 不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)
transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体
,也没有符合KVC相关属性,transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性
。
动画组
利用CAAnimationGroup
可以把动画组合在一起,组成动画组。
let bezierPath=UIBezierPath()
bezierPath.moveToPoint(CGPointMake(50, 250))
bezierPath.addCurveToPoint(CGPointMake(300, 150),
controlPoint1: CGPointMake(75, 0),
controlPoint2: CGPointMake(225, 300))
let pathLayer=CAShapeLayer()
pathLayer.path=bezierPath.CGPath
pathLayer.fillColor=UIColor.clearColor().CGColor
pathLayer.strokeColor=UIColor.redColor().CGColor
pathLayer.lineWidth=3.0
self.view.layer.addSublayer(pathLayer)
let colorLayer=CALayer()
colorLayer.frame=CGRectMake(50, 250, 64, 64)
colorLayer.position=CGPointMake(50, 250)
colorLayer.backgroundColor=UIColor.greenColor().CGColor
self.view.layer.addSublayer(colorLayer)
let animation1=CAKeyframeAnimation()
animation1.keyPath="position"
animation1.path=bezierPath.CGPath
animation1.rotationMode=kCAAnimationRotateAuto
let animation2=CABasicAnimation()
animation2.keyPath="backgroundColor"
animation2.toValue=UIColor.redColor().CGColor
let groupAnimation=CAAnimationGroup()
groupAnimation.animations=[animation1,animation2]
groupAnimation.duration=4.0
colorLayer.addAnimation(groupAnimation, forKey: nil)
过渡
使用CATransition
创建一个过渡动画,它有一个type
和subtype
来标识变换效果。type有如下四种类型:
- kCATransitionFade:平滑的淡入淡出效果
- kCATransitionMoveIn:顶部滑动进入
- kCATransitionPush:新图层一侧滑动进来,旧图层从另一侧推出去
- kCATransitionReveal:把原始的图层滑动出去来显示新的外观
subtype控制它们的滑入方向:
- kCATransitionFromRight
- kCATransitionFromLeft
- kCATransitionFromTop
- kCATransitionFromBottom
自定义动画
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, true, 0.0)
self.view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let coverImage:UIImage=UIGraphicsGetImageFromCurrentImageContext()
let coverView=UIImageView(image: coverImage)
coverView.frame=self.view.bounds
self.view.addSubview(coverView)
let red:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let green:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let blue:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
self.view.backgroundColor=UIColor(red: red, green: green, blue: blue, alpha: 1.0)
UIView.animateWithDuration(1.0, animations: { () -> Void in
var transform:CGAffineTransform=CGAffineTransformMakeScale(0.01, 0.01)
transform=CGAffineTransformRotate(transform,CGFloat(M_PI_2))
coverView.transform=transform
coverView.alpha=0.0
}) { (finished) -> Void in
coverView.removeFromSuperview()
}
图层时间
CAMediaTiming协议
CAMediaTiming协议
定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
CAMediaTiming除了有duration
属性还有另外一个属性叫repeatCount
,代表动画重复的迭代次数。
创建重复动画的另外一种方式是使用repeatDuration
属性,它让动画重复一个指定时间,而不是指定次数。设置一个叫做autoreverses
的属性(BOOL类型)在每次间隔交替循环过程中自动回放。
注意:repeatCount和repeatDuration可能会相互冲突,所以你只要对其中一个指定非零值。
相对时间:
时间都是相对的,每个动画都有它自己的描述的时间,可以独立地加速,延时或者偏移。
beginTime
指定了动画开始之前的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0。
speed
是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。
timeOffset
和beginTime
类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。
注:和beginTime不同的是,timeOffset并不受speed的影响。
fillMode:保持动画开始之前那一帧,或者动画结束之后的那一帧。这就是所谓的填充,因为动画开始和结束的值用来填充开始之前和结束之后的时间。
这就是被CAMediaTiming的fillMode
来控制,有如下四种常量:
- kCAFillModeForwards
- kCAfillModeBackwards
- kCAFillModeBoth
- kCAFillModeRemoves
未完待续......