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很活躍的時候的確會暫停一些別的任務。
#####物理模擬