iOS核心動畫高級技巧之圖層變換和專用圖層(二)
iOS核心動畫高級技巧之核心動畫(三)
iOS核心動畫高級技巧之性能(四)
iOS核心動畫高級技巧之動畫總結(五)
隱式動畫
隱式動畫主要作用于CALayer的可動畫屬性上面,UIView對應的layer是不可以的,只要你改變屬性的值,它不是突兀的直接改變過去,而是一個有一個動畫的過程,這個時間等屬性你可以通過事務(CATransaction)來控制,如果你不自己提供一個事務,它的默認時間是0.25秒,當然這個可動畫屬性是需要觸發的,如果你一上來就設置一個值,可能看不到動畫效果.
1 redLayer = CALayer() 2 redLayer.backgroundColor = UIColor.redColor().CGColor 3 redLayer.frame = CGRectMake(50, 100, 100, 100) 4 self.view.layer.addSublayer(redLayer) 5 6 NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: NSSelectorFromString("animate"), userInfo: nil, repeats: false) 7 8 func animate() { 9 CATransaction.begin()10 CATransaction.setAnimationDuration(12)11 12 var redC = CGFloat(arc4random() % 256 ) / 255.013 var greenC = CGFloat(arc4random() % 256 ) / 255.014 var blueC = CGFloat(arc4random() % 256 ) / 255.015 16 self.redLayer.backgroundColor = UIColor(red: redC, green: greenC, blue: blueC, alpha: 1).CGColor17 18 CATransaction.commit()19 }
在上面這個transaction中加上一個完成塊可以使它在在動畫完成的時候做一個0.25秒的默認動畫,demo中有兩種動畫方式都可以(2d和3d)
1 CATransaction.begin() 2 CATransaction.setAnimationDuration(3) 3 CATransaction.setCompletionBlock { () -> Void in 4 /*3d動畫 5 var transform = self.redLayer.transform 6 transform = CATransform3DRotate(transform, CGFloat(M_PI_4), 0, 0, 1) 7 self.redLayer.transform = transform 8 */ 9 //這個是2d的動畫10 var transform = self.redLayer.affineTransform()11 transform = CGAffineTransformRotate(transform, CGFloat(M_PI_4))12 self.redLayer.setAffineTransform(transform)
layer之所以能做隱式動畫是因為對應的屬性有對應的action,這個action可以通過layer的delegate的代理方法actionforLayer:forkey獲得,也可以通過設置layer的actions屬性實現,兩種方法都之所以UIView沒有隱式動畫,是因為它對應的layer對應的delegate是它自己,而它的actionforlayer:forkey方法每次都是返回nil,所以它沒有對應的action,所以不能做隱式動畫,如果你想讓一個view有隱式動畫的話可以重寫它的actionforlayer方法,跟它對應的key返回一個action,這個action的類型是CAtransition類型.另外,在UIView的beginAnimations和commitAnimations方法中間它的actionfoylayer方法會有返回值可以做隱式動畫,如果你不想讓普通的layer做隱式動畫可以調用CATransaction的setDisableActions方法禁止,直接設置actions為nil沒什么反應
1 var transition = CATransition()2 transition.type = kCATransitionPush//設置出現方式,默認為fade3 transition.subtype = kCATransitionFromLeft//設置出現方向4 redLayer.actions = ["backgroundColor":transition]
layer能做隱式動畫,但是你獲取可動畫的屬性它的值還是最后設置的值,那是因為它并不是一個layer生成的動畫,它有modelLayer一般是返回layer本身,它還有一個PResenttationLayer呈現圖層,我們設置的值是給modelLayer的,而做動畫的是presentationLayer.有兩種情況你可能需要用到呈現圖層,一種是你需要獲得動畫過程中layer的位置,另一種是你需要在動畫過程中響應用戶交互.下面這個demo效果是如果你點擊到了方塊則變顏色,沒點到則方塊移動到你點擊的位置
1 redLayer = CALayer() 2 redLayer.backgroundColor = UIColor.redColor().CGColor 3 redLayer.frame = CGRectMake(50, 100, 100, 100) 4 self.view.layer.addSublayer(redLayer) 5 6 override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) { 7 var point = ( touches as NSSet ) .anyObject()?.locationInView(self.view) 8 9 if (self.redLayer.presentationLayer().hitTest(point!) != nil) {10 var redC = CGFloat(arc4random() % 256 ) / 255.011 var greenC = CGFloat(arc4random() % 256 ) / 255.012 var blueC = CGFloat(arc4random() % 256 ) / 255.013 14 self.redLayer.backgroundColor = UIColor(red: redC, green: greenC, blue: blueC, alpha: 1).CGColor15 }else {16 CATransaction.begin()17 CATransaction.setAnimationDuration(2.0)18 self.redLayer.position = point!19 CATransaction.commit()20 }21 }
顯式動畫
CABasicAnimation(屬性動畫)
CABasicAnimation動畫和隱式動畫類似,它可以設置起始和結束值和delegate,下面這個例子和上面的隱式動畫類似
1 redLayer = CALayer() 2 redLayer.backgroundColor = UIColor.redColor().CGColor 3 redLayer.frame = CGRectMake(50, 100, 100, 100) 4 self.view.layer.addSublayer(redLayer) 5 6 NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: NSSelectorFromString("animate"), userInfo: nil, repeats: false) 7 } 8 9 func animate () {10 var redC = CGFloat(arc4random() % 256 ) / 255.011 var greenC = CGFloat(arc4random() % 256 ) / 255.012 var blueC = CGFloat(arc4random() % 256 ) / 255.013 var color = UIColor(red: redC, green: greenC, blue: blueC, alpha: 1).CGColor14 15 var animate = CABasicAnimation()16 animate.duration = 8.017 animate.keyPath = "backgroundColor"18 animate.toValue = color19 animate.delegate = self20 self.redLayer .addAnimation(animate, forKey: nil)21 }22 23 override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {24 CATransaction.begin()25 26 CATransaction.setDisableActions(true)27 var animate = anim as! CABasicAnimation28 self.redLayer.backgroundColor = animate.toValue as! CGColorRef29 CATransaction.commit()30 }
注意在代碼中,stop的代理方法中的隱式動畫我們是禁用的,要不然它會做兩次動畫,如果是view的layer動畫就不需要,因為view的隱式動畫默認就禁止了,如果有多個動畫需要代理方法,可以在添加動畫的時候設置key,在代理方法中通過key獲取animate.還有更簡便的KVC來獲取,animate.setValue(redView,forKey:"redView"),在代理方法中用valueForKey獲取
CAKeyframeAnimation(關鍵幀動畫)
關鍵幀動畫見名知意,你設置values屬性中的每一幀的值,然后iOS就會按照你設置的值來做動畫,你還可以對應設置它對應的時間,這個很強大后面會說到,現在先說另一個強大的功能,它可以沿著路徑來做動畫,只要跟它的path屬性設置一個CGBezierPathRef類型的值就可以了.其實沿著路徑做動畫就和設置一個個values的值是一樣的,路徑也是由一個個position的值的點組成的,沿著路徑做動畫,物體要跟著路徑調整方向,你可以同時跟它的方向做rotate動畫,但是可能引起沖突或其它問題,它又一個rotationMode 直接設置成rotateAuto就可以了.demo很簡單,列舉了.
還有一點需要注意的是:在做transform做動畫時用transform.rotation等來做動畫會好很多,一方面可以用byValue來設置值,另一方面position/scale/rotation也不會有沖突.你直接設置transform.position的值其實本身是沒有用的,因為它就沒有這個屬性,只是iOS內部用KVC把transform.position的值用CAValueFuction轉換成了transform對應的矩陣值
CAAnimationGroup(組動畫)
CAAnimationGroup動畫組也是顧名思義可以做一個動畫組,只要跟它的animations屬性設置一個動畫數組就行了,每個數組項的值是一個basicAnimation或者keyframeAnimation
CATransition(過渡動畫)
這里首先需要注意的是CATransition是一個動畫,而CATransaction是一個動畫事務,都是核心動畫里面的兩個概念,所以要區別開來.應該說CATransition是核心動畫里最簡單最好用但是最容易被人忽略的動畫了,下面跟imageview設置image的時候給一個fadein的動畫,你看是多簡單.
1 NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: NSSelectorFromString("animate"), userInfo: nil, repeats: false) 2 3 self.img = UIImageView(image: UIImage(named: "111.png")) 4 self.view.addSubview(self.img) 5 } 6 7 func animate () { 8 var transition = CATransition() 9 transition.type = kCATransitionFade10 self.img.layer .addAnimation(transition, forKey: nil)11 12 self.img.image = UIImage(named: "222.png")13 }
對于CAtransition來說,在自己創建的layer中,它是默認加上的,而在view關聯的屠城,它是被禁用的,畢竟它還是得提供你一種簡單的正常的設置屬性的方法,它是對整個圖層樹都有效,如果添加了transition會跟它的子圖層都加上整個效果,比如跟tabbar切換的漸變效果(大多數VC切換都可以在代理方法或其它方法中寫動畫)
1 var root = UITabBarController() 2 root.viewControllers = [ViewController(),OneViewController()] 3 root.delegate = self 4 self.rootVC = root 5 6 self.window?.rootViewController = root 7 self.window?.makeKeyAndVisible() 8 9 return true10 }11 func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {12 var transition = CATransition()13 transition.type = kCATransitionFade14 transition.duration = 315 self.rootVC.view.layer.addAnimation(transition, forKey: nil)16 }
UiView提供了transitionFromView的方法,如果只跟一個view做動畫直接可以用它和加上transition動畫的效果是一樣的,但是一般它對子圖層不起作用,在動畫運行的過程中可以remove掉動畫,一個按鈕添加動畫,一個按鈕控制刪除動畫,而動畫是使用的byValue,所以效果就是看起來是暫停.
下面的代碼做的是暫停動畫的功能,在暫停的時候更新model樹為presentlayer的值.
1 redLayer = CALayer() 2 redLayer.backgroundColor = UIColor.redColor().CGColor 3 redLayer.frame = CGRectMake(50, 100, 100, 100) 4 self.view.layer.addSublayer(redLayer) 5 6 var beginBtn = UIButton(frame: CGRectMake(50, 300, 100, 30)) 7 beginBtn.setTitle("開始", forState: UIControlState.Normal) 8 beginBtn.addTarget(self, action: "begin", forControlEvents: UIControlEvents.TouchUpInside) 9 self.view.addSubview(beginBtn)10 11 var stopBtn = UIButton(frame: CGRectMake(200, 300, 100, 30))12 stopBtn.setTitle("暫停", forState: UIControlState.Normal)13 stopBtn.addTarget(self, action: "stop", forControlEvents: UIControlEvents.TouchUpInside)14 self.view.addSubview(stopBtn)15 }16 17 func begin() {18 var animate = CABasicAnimation()19 animate.keyPath = "transform.rotation"20 animate.duration = 2.021 animate.byValue = CGFloat(M_PI / 4)22 animate.delegate = self23 // animate.fillMode = kCAFillModeForwards24 // animate.removedOnCompletion = false25 self.redLayer .addAnimation(animate, forKey: "animate")26 }27 func stop() {28 self.redLayer.transform = self.redLayer.presentationLayer().transform29 self.redLayer.removeAnimationForKey("animate")30 }
圖層時間
beginTime/speed/timeoffset 三個動畫屬性都是相對概念,分別表示對應duration的開始時間,動畫的速度(會改變動畫結束的時間),讓動畫瞬間快進到某一點.下面是讓動畫暫停的第二種方法.
1 redLayer = CALayer() 2 redLayer.backgroundColor = UIColor.redColor().CGColor 3 redLayer.frame = CGRectMake(50, 100, 100, 100) 4 self.view.layer.addSublayer(redLayer) 5 6 var beginBtn = UIButton(frame: CGRectMake(50, 300, 100, 30)) 7 beginBtn.setTitle("開始", forState: UIControlState.Normal) 8 beginBtn.addTarget(self, action: "resumeLayer", forControlEvents: UIControlEvents.TouchUpInside) 9 self.view.addSubview(beginBtn)10 11 var stopBtn = UIButton(frame: CGRectMake(200, 300, 100, 30))12 stopBtn.setTitle("暫停", forState: UIControlState.Normal)13 stopBtn.addTarget(self, action: "pauseLayer", forControlEvents: UIControlEvents.TouchUpInside)14 self.view.addSubview(stopBtn)15 16 self.pauseTime = 0.017 self.startAnimate()18 }19 20 func startAnimate () {21 var animate = CABasicAnimation()22 animate.keyPath = "transform.rotation"23 animate.duration = 20.024 animate.byValue = CGFloat(M_PI * 10)25 animate.delegate = self26 self.redLayer .addAnimation(animate, forKey: "animate")27 }28 29 func pauseLayer () {30 //獲取當前動畫的時間31 self.pauseTime = self.redLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)32 //停止運動33 self.redLayer.speed = 0.034 //設置它呆在目前的狀態不變,不然因為speed為0,layer變回了最初動畫的值35 self.redLayer.timeOffset = self.pauseTime36 }37 38 func resumeLayer () {39 //獲取暫停開始的時間40 var pausedTime = self.pauseTime41 //設置速度timeOffset等為正常值42 self.redLayer.speed = 1.043 self.redLayer.timeOffset = 0.044 self.redLayer.beginTime = 0.045 46 var timeSincePause = self.redLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime47 //設置開始時間為初始和現在的時間差48 self.redLayer.beginTime = timeSincePause49 }
removeOnCompletion 設置為no,將會在動畫結束后仍然保持前一步的狀態,然后把fillMode設置成modeforwards就可以讓它動畫執行后保持在原界面,不用回到初始值.
動畫速度
設置CAAnimation
的timingFunction
屬性可以控制動畫的速度,而CAKeyframeAnimation
有一個NSArray
類型的timingFunctions
屬性,它需要values這個數組長度和它的長度比它大1,這樣就可以控制每一段動畫的速度了。timingFunction的值是CAMediaTimingFunction類型的,它還有一個初始化方法可以自定義時間曲線,它是一個三次貝塞爾緩沖函數,可以通過起始點、終點、兩個控制點來初始化,默認的CAMediaTimingFunction值其實可以這樣初始化得來。
對于設置values數組長度為5,timingFunctions數組長度也為4,相當于做了四段關鍵幀動畫,而這每段動畫的時間曲線由timingFunction的每個項來控制,而這4段動畫的時間則是均分during的時間,你還可以設置keyTimes的值,它也是一個數組,它和timingFunction的數組長度一樣,控制每段動畫的時間。用這個特性幾乎可以做大多數規則動畫了。
通過上面的方法我們已經基本能夠做任何動畫了,但是我要做一個彈簧效果或者球落地的效果都需要設置多個values和timingFunction和keytimes,而且需要精確計算還效果不逼真。所以我們直接可以設置它的values值就是動畫的路徑,由于時間是平均的,而values值不同,最后就會產生速度上的差異,就會產生絢麗的效果。產生路徑的函數這個網站有提供:http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
定時器
上面我們說過設置一系列的values,然后根據時間間隔相同來做動畫,既然添加這個關鍵幀動畫只是讓它在每個相同的時間內運動一定的已知的距離,這直接用NSTime就可以解決了,每一次遍歷values數組的值,讓它位移到那就可以了。其實這就是核心動畫的本質,它存在一個問題就是NSTime是添加到NSRunloop中的,而iOS每個線程管理著一個NSRUNloop,它的每次事件是添加到任務列表里去,權限比較低,如果一個屏幕有很多動畫的話就有可能有延遲,然后就可能出現卡頓的現象,動畫就不流暢,你還可以用CADisplayLink代替它,它和NSTime的原理是一樣的,都是 一定時間執行一個方法,而且他們可以設置自己的優先級,而不同的是NSTime是被添加到任務列表中,它在屏幕刷新的時候就一定會調用一次,屏幕刷新率一般是60次每秒,而NSTime是添加到任務列表中,它會被其它任務阻塞,在主線程的任務有 處理觸摸事件、發送和接受網絡數據包、執行使用gcd的代碼、處理計時器行為、屏幕重繪等。FB的POP框架也是使用了CADisplayLink。
新聞熱點
疑難解答