Core Animation总结(一)图层变换(平面 立体)html
#Core Animationide
###图层时间 CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,因此时间能够被任意基于一个图层或者一段动画的类控制。函数
duration(CAMediaTiming的属性之一)是一个CFTimeInterval的类型(相似于NSTimeInterval的一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。oop
CAMediaTiming另外还有一个属性叫作repeatCount,表明动画重复的迭代次数。若是duration是2,repeatCount设为3.5(三个半迭代),那么完整的动画时长将是7秒。性能
duration和repeatCount默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅表明了“默认”,也就是0.25秒和1次
let animation: CABasicAnimation = CABasicAnimation() animation.keyPath = "transform.rotation" // 持续时间 animation.duration = 2.0 // 次数 animation.repeatCount = 2 animation.byValue = CGFloat.pi*2 shipLayer.add(animation, forKey: "rotateAnimation")
建立重复动画的另外一种方式是使用repeatDuration属性,它让动画重复一个指定的时间,而不是指定次数。你甚至设置一个叫作autoreverses的属性(BOOL类型)在每次间隔交替循环过程当中自动回放。这对于播放一段连续非循环的动画颇有用
开门关门的效果
shipLayer = CALayer() shipLayer.frame = CGRect(x: 100, y: 100, width: 150, height: 200) shipLayer.position = CGPoint(x: 150, y: 200) shipLayer.anchorPoint = CGPoint(x: 0, y: 0.5) shipLayer.contents = UIImage(named: "bg.jpg")?.cgImage shipLayer.borderWidth=1 self.view.layer.addSublayer(shipLayer) // 透视效果 var transform: CATransform3D = CATransform3DIdentity // 首先要实现view(layer)的透视效果(就是近大远小),是经过设置m34的:m34= -1/D,D越小透视效果越明显。 transform.m34 = -1/500 self.view.layer.sublayerTransform = transform let animation: CABasicAnimation = CABasicAnimation() animation.keyPath = "transform.rotation.y" animation.toValue = -CGFloat.pi/2 animation.duration = 2 /* repeatDuration让动画重复一个指定的时间,而不是指定次数 把repeatDuration设置为INFINITY,因而动画无限循环播放,设置repeatCount为INFINITY也有一样的效果 repeatCount和repeatDuration可能会相互冲突,因此你只要对其中一个指定非零值 */ animation.repeatDuration = 2 //每次间隔交替循环过程当中自动回放 animation.autoreverses = true shipLayer.add(animation, forKey: nil)
beginTime指定了动画开始以前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0 speed是一个时间的倍数,默认1.0,减小它会减慢图层/动画的时间,增长它会加快速度。若是2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了 timeOffset和beginTime相似,可是和增长beginTime致使的延迟动画不一样,增长timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来讲,设置timeOffset为0.5意味着动画将从一半的地方开始 把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束的地方开始,由于1秒的动画实际上被缩短到了0.5秒。然而即便使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,而后从头开始播放。
let v: UIView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100)) v.backgroundColor = UIColor.blue self.view.addSubview(v) // 贝塞尔曲线 let bezierPath: UIBezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 150, y: 0)) bezierPath.addCurve(to: CGPoint(x: 150, y: 300), controlPoint1: CGPoint(x: 0, y: 75), controlPoint2: CGPoint(x: 300, y: 225)) let pathLayer: CAShapeLayer = CAShapeLayer() pathLayer.path = bezierPath.cgPath pathLayer.fillColor = UIColor.clear.cgColor pathLayer.strokeColor = UIColor.red.cgColor pathLayer.lineWidth = 3 v.layer.addSublayer(pathLayer) let shipLayer: CALayer = CALayer() shipLayer.frame = CGRect(x: 0, y: 0, width: 50, height: 50) shipLayer.position = CGPoint(x: 0, y: 150) shipLayer.contents = UIImage(named: "bg.jpg")?.cgImage v.layer.addSublayer(shipLayer) let animation: CAKeyframeAnimation = CAKeyframeAnimation() animation.path = bezierPath.cgPath animation.keyPath = "position" /* beginTime指定了动画开始以前的的延迟时间。 这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会马上执行)。 */ animation.beginTime = 1 /* 增长timeOffset只是让动画快进到某一点,timeOffset并不受speed的影响 例如, 对于一个持续duration = 4秒的动画来讲,设置timeOffset为2 意味着动画将从一半的地方开始,到结束,而后从开始的地方到一半的地方,仍然播放了一个完整的时长 */ animation.timeOffset = 2 // CFTimeInterval /* speed是一个时间的倍数,默认1.0,减小它会减慢图层/动画的时间,增长它会加快速度。 若是把图层的speed设置成0,它会暂停任何添加到图层上的动画。相似的,设置speed大于1.0将会快进,设置成一个负值将会倒回动画。 若是2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。 */ animation.speed = 1.0 // float animation.duration = 4 //kCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转 animation.rotationMode = kCAAnimationRotateAuto //removeOnCompletion被设置为NO的动画将会在动画结束的时候仍然保持以前的状态 animation.isRemovedOnCompletion = false /* 默认是kCAFillModeRemoved,当动画再也不播放的时候就显示图层模型指定的值剩下的三种类型向前,向后或者即向前又向后去填充动画状态,使得动画在开始前或者结束后仍然保持开始和结束那一刻的值。 使用时,须要 isRemovedOnCompletion = false */ animation.fillMode = kCAFillModeRemoved shipLayer.add(animation, forKey: nil)
对CALayer或者CAGroupAnimation调整duration和repeatCount/repeatDuration属性并不会影响到子动画。 可是beginTime,timeOffset和speed属性将会影响到子动画。 然而在层级关系中,beginTime指定了父图层开始动画(或者组合关系中的父动画)和对象将要开始本身动画之间的偏移。 调整CALayer和CAGroupAnimation的speed属性将会对动画以及子动画速度应用一个缩放的因子。
#####全局时间和本地时间 CoreAnimation有一个全局时间的概念,也就是所谓的马赫时间(“马赫”其实是iOS和Mac OS系统内核的命名)。马赫时间在设备上全部进程都是全局的,真实的做用在于对动画的时间测量提供了一个相对值。注意当设备休眠的时候马赫时间会暂停,也就是全部的CAAnimations(基于马赫时间)一样也会暂停。使用 CACurrentMediaTime
函数来访问马赫时间
每一个CALayer和CAAnimation实例都有本身本地时间的概念,是根据父图层/动画层级关系中的beginTime,timeOffset和speed属性计算。就和转换不一样图层之间坐标关系同样,CALayer一样也提供了方法来转换不一样图层之间的本地时间
open func convertTime(_ t: CFTimeInterval, from l: CALayer?) -> CFTimeInterval open func convertTime(_ t: CFTimeInterval, to l: CALayer?) -> CFTimeInterval
###缓冲 设置CAAnimation的timingFunction属性,是CAMediaTimingFunction类的一个对象。若是想改变隐式动画的计时函数,一样也可使用CATransaction的+setAnimationTimingFunction:方法。
这里有一些方式来建立CAMediaTimingFunction,最简单的方式是调用+timingFunctionWithName:的构造方法。
var v: UIView! var layer: CALayer! override func viewDidLoad() { super.viewDidLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapAction(sender:)))) v = UIView(frame: CGRect(x: 30, y: 30, width: 50, height: 50)) v.backgroundColor = UIColor.gray self.view.addSubview(v) layer = CALayer() layer.frame = CGRect(x: 50, y: 50, width: 100, height: 100) layer.opacity = 0.3 layer.backgroundColor = UIColor.red.cgColor self.view.layer.addSublayer(layer) } func tapAction(sender:UITapGestureRecognizer){ /* // 1.CALayer 动画缓冲 // 开始一个新的事务 CATransaction.begin() // 持续时间 CATransaction.setAnimationDuration(2.0) /* CAAnimation的timingFunction属性,是CAMediaTimingFunction类的一个对象 kCAMediaTimingFunctionLinear 默认函数 一个线性的计时函数 线性步调对于那些当即加速而且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹) kCAMediaTimingFunctionEaseIn常量建立了一个慢慢加速而后忽然中止的方法。对于以前提到的自由落体的例子来讲很适合,或者好比对准一个目标的导弹的发射。 kCAMediaTimingFunctionEaseOut它以一个全速开始,而后慢慢减速中止。它有一个削弱的效果,应用的场景好比一扇门慢慢地关上 kCAMediaTimingFunctionEaseInEaseOut建立了一个慢慢加速而后再慢慢减速的过程 kCAMediaTimingFunctionDefault,它和kCAMediaTimingFunctionEaseInEaseOut很相似,可是加速和减速的过程都稍微有些慢。它和kCAMediaTimingFunctionEaseInEaseOut的区别很难察觉,当建立显式的CAAnimation它并非默认选项 */ CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut )) layer.position = sender.location(in: self.view) // 提交事务 CATransaction.commit() */ // 2.关键帧动画缓冲 给图层的颜色变化添加一点脉冲效果 let animation: CAKeyframeAnimation = CAKeyframeAnimation() animation.keyPath = "backgroundColor" animation.duration = 4.0 animation.values = [ UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor, UIColor.gray.cgColor, UIColor.red.cgColor ] let fn: CAMediaTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) /* CAKeyframeAnimation有一个NSArray类型的timingFunctions属性, 咱们能够用它来对每次动画的步骤指定不一样的计时函数。 可是指定函数的个数必定要等于keyframes数组的元素个数减一,由于它是描述每一帧之间动画速度的函数。 */ animation.timingFunctions = [fn,fn,fn,fn] layer.add(animation, forKey: nil) // 3.UIView 动画缓冲 /* UIKit的动画也一样支持这些缓冲方法的使用,尽管语法和常量有些不一样, 为了改变UIView动画的缓冲选项,参数添加options */ UIView.animate( withDuration: 2.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: { self.v.center = sender.location(in: self.view) }, completion: {(b:Bool) in }) }
#####三次贝塞尔曲线 CAMediaTimingFunction函数的主要原则在于它把输入的时间转换成起点和终点之间成比例的改变。 x轴表明时间,y轴表明改变的量,因而线性的缓冲就是一条从起点开始的简单的斜线(x=y,x>=0) CAMediaTimingFunction使用了一个叫作三次贝塞尔曲线的函数,一个三次贝塞尔曲线经过四个点来定义,第一个和最后一个点表明了曲线的起点和终点,剩下中间两个点叫作控制点,由于它们控制了曲线的形状,贝塞尔曲线的控制点实际上是位于曲线以外的点,也就是说曲线并不必定要穿过它们。
使用UIBezierPath绘制CAMediaTimingFunction
//曲线的起始和终点始终是{0, 0}和{1, 1},因而咱们只须要检索曲线的第二个和第三个点(控制点) //create timing function CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]; //get control points CGPoint controlPoint1, controlPoint2; //用来检索曲线的点 [function getControlPointAtIndex:1 values:(float *)&controlPoint1]; [function getControlPointAtIndex:2 values:(float *)&controlPoint2]; //用UIBezierPath和CAShapeLayer来把它画出来 //create curve UIBezierPath *path = [[UIBezierPath alloc] init]; [path moveToPoint:CGPointZero]; [path addCurveToPoint:CGPointMake(1, 1) controlPoint1:controlPoint1 controlPoint2:controlPoint2]; //scale the path up to a reasonable size for display [path applyTransform:CGAffineTransformMakeScale(200, 200)]; //create shape layer CAShapeLayer *shapeLayer = [CAShapeLayer layer]; shapeLayer.strokeColor = [UIColor redColor].CGColor; shapeLayer.fillColor = [UIColor clearColor].CGColor; shapeLayer.lineWidth = 4.0f; shapeLayer.path = path.CGPath; [self.layerView.layer addSublayer:shapeLayer]; //flip geometry so that 0,0 is in the bottom-left self.layerView.layer.geometryFlipped = YES;
#####基于关键帧的缓冲 使用关键帧实现反弹球的动画
var v: UIView! var layer: CALayer! override func viewDidLoad() { super.viewDidLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapAction(sender:)))) v = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100)) v.backgroundColor = UIColor.gray self.view.addSubview(v) } func tapAction(sender:UITapGestureRecognizer){ v.center = CGPoint(x: 150, y: 50) //使用关键帧实现反弹球的动画 let animation: CAKeyframeAnimation = CAKeyframeAnimation() animation.keyPath = "position" animation.duration = 2 animation.values = [ CGPoint(x: 150, y: 50), CGPoint(x: 150, y: 200), CGPoint(x: 150, y: 100), CGPoint(x: 150, y: 200), CGPoint(x: 150, y: 150), CGPoint(x: 150, y: 200), CGPoint(x: 150, y: 180), CGPoint(x: 150, y: 200) ] //指定函数的个数必定要等于keyframes数组的元素个数减一,由于它是描述每一帧之间动画速度的函数。 animation.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) ] //keyTimes来指定每一个关键帧的时间偏移,因为每次反弹的时间都会减小,因而关键帧并不会均匀分布 animation.keyTimes = [ 0.0, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0 ] v.layer.position = CGPoint(x: 150, y: 200) v.layer.add(animation, forKey: nil) }
###基于定时器的动画
#####定时帧
NSTimer iOS上的每一个线程都管理了一个NSRunloop,字面上看就是经过一个循环来完成一些任务列表。可是对主线程,这些任务包含以下几项:
当你设置一个NSTimer,他会被插入到当前任务列表中,而后直到指定时间过去以后才会被执行。可是什么时候启动定时器并无一个时间上限,并且它只会在列表中上一个任务完成以后开始执行。这一般会致使有几毫秒的延迟,可是若是上一个任务过了好久才完成就会致使延迟很长一段时间。 屏幕重绘的频率是一秒钟六十次,可是和定时器行为同样,若是列表中上一个执行了很长时间,它也会延迟。这些延迟都是一个随机值,因而就不能保证定时器精准地一秒钟执行六十次。有时候发生在屏幕重绘以后,这就会使得更新屏幕会有个延迟,看起来就是动画卡壳了。有时候定时器会在屏幕更新的时候执行两次,因而动画看起来就跳动了。
CADisplayLink CADisplayLink是CoreAnimation提供的另外一个相似于NSTimer的类,它老是在屏幕完成一次更新以前启动,它的接口设计的和NSTimer很相似,因此它实际上就是一个内置实现的替代,可是和timeInterval以秒为单位不一样,CADisplayLink有一个整型的frameInterval属性,指定了间隔多少帧以后才执行。默认值是1,意味着每次屏幕更新以前都会执行一次。可是若是动画的代码执行起来超过了六十分之一秒,你能够指定frameInterval为2,就是说动画每隔一帧执行一次(一秒钟30帧)或者3,也就是一秒钟20次,等等。 用CADisplayLink而不是NSTimer,会保证帧率足够连续,使得动画看起来更加平滑,但即便CADisplayLink也不能保证每一帧都按计划执行,一些失去控制的离散的任务或者事件(例如资源紧张的后台程序)可能会致使动画偶尔地丢帧。当使用NSTimer的时候,一旦有机会计时器就会开启,可是CADisplayLink却不同:若是它丢失了帧,就会直接忽略它们,而后在下一次更新的时候接着运行。
帧的持续时间 不管是使用NSTimer仍是CADisplayLink,咱们仍然须要处理一帧的时间超出了预期的六十分之一秒。因为咱们不可以计算出一帧真实的持续时间,因此须要手动测量。咱们能够在每帧开始刷新的时候用CACurrentMediaTime()记录当前时间,而后和上一帧记录的时间去比较。咱们就能够获得真实的每帧持续的时间,而后代替硬编码的六十分之一秒。
Run Loop 当建立CADisplayLink的时候,咱们须要指定一个run loop和run loop mode,对于run loop来讲,咱们就使用了主线程的run loop,由于任何用户界面的更新都须要在主线程执行,可是模式的选择就并不那么清楚了,每一个添加到run loop的任务都有一个指定了优先级的模式,为了保证用户界面保持平滑,iOS会提供和用户界面相关任务的优先级,并且当UI很活跃的时候的确会暂停一些别的任务。
#####物理模拟