動畫是Core Animation庫一個非常顯著的特性。這一章我們來看看它是怎么做到的经柴。具體來說丈探,我們先來討論框架自動完成的隱式動畫(除非你明確禁用了這個功能)欺殿。
事務
- 事務是Core Animation用來包含一系列屬性動畫集合的機制搂擦,任何用指定事務去改變可以做動畫的圖層屬性都不會立刻發(fā)生變化稳诚,而是當事務一旦提交的時候開始用一個動畫過渡到新值。
事務是通過CATransaction
類來做管理瀑踢,可以用+begin
和+commit
分別來入棧或者出棧才避。CATransaction
的+begin
和+commit
方法與UIView
的+beginAnimations:context:
和+commitAnimations
方法類似橱夭。
任何可以做動畫的圖層屬性都會被添加到棧頂?shù)氖聞眨憧梢酝ㄟ^+setAnimationDuration:
方法設置當前事務的動畫時間桑逝,或者通過+animationDuration
方法來獲取值(默認0.25秒)棘劣。
完成塊
-
基于
UIView
的block的動畫允許你在動畫結(jié)束的時候提供一個完成的動作。CATranscation
接口提供的+setCompletionBlock:
方法也有同樣的功能楞遏。來看一個例子茬暇,首先創(chuàng)建一個藍色的方塊,然后添加一個按鈕寡喝,隨機改變它的顏色糙俗,再添加一個完成之后的block,用來在每次顏色變化結(jié)束之后切換到另一個旋轉(zhuǎn)45度的動畫预鬓。點擊按鈕巧骚,你會發(fā)現(xiàn)圖層的顏色平滑過渡到一個新值,而不是跳變格二。并且旋轉(zhuǎn)動畫要比顏色漸變快得多劈彪,這是因為完成塊是在顏色漸變的事務提交并出棧之后才被執(zhí)行,于是顶猜,用默認的事務做變換沧奴,默認的時間也就變成了0.25秒。
var colorLayer = CALayer()
@IBOutlet weak var layerView: UIView!
@IBAction func changeColor(sender: UIButton) {
CATransaction.begin()
CATransaction.setAnimationDuration(2.0)let red = CGFloat(arc4random()) / CGFloat(INT_MAX) let green = CGFloat(arc4random()) / CGFloat(INT_MAX) let blue = CGFloat(arc4random()) / CGFloat(INT_MAX) self.colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).CGColor CATransaction.setCompletionBlock({ var transform = self.colorLayer.affineTransform() transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2) / 2.0) self.colorLayer.setAffineTransform(transform) }) CATransaction.commit() } override func viewDidLoad() { self.colorLayer.frame = CGRectMake(50, 50, 100, 100) self.colorLayer.backgroundColor = UIColor.blueColor().CGColor self.layerView.layer.addSublayer(self.colorLayer) }
圖層行為
-
對上一個例子做一點修改长窄,移除
colorLayer
滔吠,并且直接設置layerView
關(guān)聯(lián)圖層的背景色纲菌,現(xiàn)在是直接對UIView
關(guān)聯(lián)的圖層做動畫而不是一個單獨的圖層。運行程序屠凶,會發(fā)現(xiàn)當按下按鈕驰后,圖層顏色瞬間切換到新的值,而不是之前平滑過渡的動畫矗愧。這是因為隱式動畫被UIVew
關(guān)聯(lián)圖層給禁用了灶芝。
UIView
關(guān)聯(lián)的圖層禁用了隱式動畫,對這種圖層做動畫的唯一辦法就是使用UIView
的動畫函數(shù)(而不是依賴CATransaction
)唉韭,或者繼承UIView
夜涕,并覆蓋-actionForLayer:forKey:
方法,或者直接創(chuàng)建一個顯式動畫(具體細節(jié)見第七章)属愤。對于單獨存在的圖層女器,我們可以通過實現(xiàn)圖層的-actionForLayer:forKey:
委托方法,或者提供一個actions
字典來控制隱式動畫住诸。
CATransacition
有個方法叫做+setDisableActions:
驾胆,可以用來對所有屬性打開或者關(guān)閉隱式動畫。
我們來對顏色漸變的例子使用一個不同的行為贱呐,通過給colorLayer設置一個自定義的actions
字典丧诺。 行為通常是一個被Core Animation隱式調(diào)用的顯式動畫對象。這里我們使用的是一個實現(xiàn)了CATransaction
的實例奄薇,叫做推進過渡驳阎。第七章中將會詳細解釋過渡,不過對于現(xiàn)在馁蒂,知道CATransition
響應CAAction
協(xié)議呵晚,并且可以當做一個圖層行為就足夠了。結(jié)果是不論在什么時候改變背景顏色沫屡,新的色塊都是從左側(cè)滑入饵隙,而不是默認的漸變效果。
@IBOutlet weak var layerView: UIView!
var colorLayer = CALayer()@IBAction func changeColor(sender: UIButton) { CATransaction.begin() CATransaction.setAnimationDuration(2) let red = CGFloat(arc4random() / UINT32_MAX) let green = CGFloat(arc4random()) / CGFloat(INT_MAX) let blue = CGFloat(arc4random()) / CGFloat(INT_MAX) self.colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).CGColor CATransaction.commit() } override func viewDidLoad() { self.colorLayer.frame = CGRectMake(50, 50, 100, 100) self.colorLayer.backgroundColor = UIColor.blueColor().CGColor self.layerView.layer.addSublayer(self.colorLayer) let transition = CATransition() transition.type = kCATransitionPush transition.subtype = kCATransitionFromLeft self.colorLayer.actions = ["backgroundColor" : transition] }
呈現(xiàn)與模型
- 在iOS中谁鳍,屏幕每秒鐘重繪60次癞季。如果動畫時長比60分之一秒要長,Core Animation就需要在設置一次新值和新值生效之間倘潜,對屏幕上的圖層進行重新組織绷柒。這意味著
CALayer
除了“真實”值(就是你設置的值)之外,必須要知道當前顯示在屏幕上的屬性值的記錄涮因。
每個圖層屬性的顯示值都被存儲在一個叫做呈現(xiàn)圖層的獨立圖層當中废睦,他可以通過-presentationLayer
方法來訪問。這個呈現(xiàn)圖層實際上是模型圖層的復制养泡,但是它的屬性值代表了在任何指定時刻當前外觀效果嗜湃。換句話說奈应,你可以通過呈現(xiàn)圖層的值來獲取當前屏幕上真正顯示出來的值。
大多數(shù)情況下购披,你不需要直接訪問呈現(xiàn)圖層杖挣,你可以通過和模型圖層的交互,來讓Core Animation更新顯示刚陡。兩種情況下呈現(xiàn)圖層會變得很有用惩妇,一個是同步動畫,一個是處理用戶交互筐乳。
1.如果你在實現(xiàn)一個基于定時器的動畫(見第10章“基于定時器的動畫”)歌殃,而不僅僅是基于事務的動畫,這個時候準確地知道在某一時刻圖層顯示在什么位置就會對正確擺放圖層很有用了蝙云。
2.如果你想讓你做動畫的圖層響應用戶輸入氓皱,你可以使用-hitTest:
方法來判斷指定圖層是否被觸摸,這時候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:
會顯得更有意義勃刨,因為呈現(xiàn)圖層代表了用戶當前看到的圖層位置波材,而不是當前動畫結(jié)束之后的位置。
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let point = (touches as NSSet).anyObject()?.locationInView(self.view)
if ((self.colorLayer.presentationLayer()!.hitTest(point!)) != nil) {
}
}