一般我們?cè)谧鰟?dòng)畫的時(shí)弥雹,會(huì)使用到 CAAnimation 的相關(guān)類鹦筹,通過 CALayer 的 addAnimation:forKey: 方法秋秤,添加動(dòng)畫效果介衔,這種動(dòng)畫稱為顯式動(dòng)畫恨胚。還有一種動(dòng)畫被稱為隱式的動(dòng)畫,在沒有主動(dòng)添加動(dòng)畫代碼時(shí)炎咖,會(huì)自動(dòng)的產(chǎn)生動(dòng)畫效果赃泡。
我們知道 UIView 的背后,有 CALayer 作為內(nèi)容的顯示乘盼,CALayer 類似于 UIView 也具有樹型結(jié)構(gòu)急迂,可以單獨(dú)的創(chuàng)建。單獨(dú)的創(chuàng)建 CALayer 實(shí)例蹦肴,并修改它的某些屬性僚碎,會(huì)神奇的出現(xiàn)動(dòng)畫效果。
override func viewDidLoad() {
super.viewDidLoad()
animationLayer = CALayer()
animationLayer?.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
animationLayer?.backgroundColor = UIColor.red.cgColor
view.layer.addSublayer(animationLayer!)
}
@IBAction func move() {
let centerX: CGFloat = CGFloat(Int(arc4random()) % 375)
let centerY: CGFloat = CGFloat(Int(arc4random()) % 667)
let center = CGPoint(x: centerX, y: centerY)
animationLayer?.position = center
}
上述代碼使用 CALayer 創(chuàng)建一個(gè)紅色的正方形阴幌,并隨機(jī)的修改它的位置勺阐。效果是:
修改 CALayer 的 position卷中,它沒有立即出現(xiàn)在目標(biāo)位置,而會(huì)有過渡的動(dòng)畫效果渊抽,該過程為 0.25 秒蟆豫,這就是所謂的隱式動(dòng)畫。
隱式動(dòng)畫的背后懒闷,存在事務(wù)的概念十减,任何一個(gè) animatable 屬性的修改,都會(huì)默認(rèn)創(chuàng)建一個(gè) CATransaction 的事務(wù)愤估,來配置動(dòng)畫的參數(shù)帮辟。重寫 CATransaction 可以覆蓋默認(rèn)的動(dòng)畫效果。
將動(dòng)畫時(shí)間修改為五秒:
CATransaction.begin()
CATransaction.setAnimationDuration(5)
animationLayer?.position = center
CATransaction.commit()
替換成上述的代碼段玩焰,移動(dòng)的效果變慢了由驹。另外,還可以用 CATransaction.setDisableActions(true)
方法直接禁用隱式動(dòng)畫昔园。需要注意的是蔓榄,修改隱式動(dòng)畫效果的代碼,都必須要在 begin()
和 commit()
之間默刚,并且成對(duì)出現(xiàn)甥郑。
CATransaction 沒有實(shí)例方法,它像個(gè)神秘的配置工具類荤西。當(dāng)嵌套的使用 CATransaction 時(shí)壹若,它以棧式結(jié)構(gòu)管理。執(zhí)行 begin
的時(shí)皂冰,會(huì)將后面的配置信息入棧;當(dāng)?shù)?commit
時(shí)养篓,出棧配置信息秃流,處于它們之間的屬性修改是原子的,等下一次 RunLoop 到來時(shí)柳弄,開始執(zhí)行動(dòng)畫舶胀。
舉一個(gè)嵌套使用的例子:
CATransaction.begin()
CATransaction.setAnimationDuration(1)
let centerX: CGFloat = CGFloat(Int(arc4random()) % 375)
let centerY: CGFloat = CGFloat(Int(arc4random()) % 667)
let center = CGPoint(x: centerX, y: centerY)
animationLayer?.position = center
CATransaction.begin()
CATransaction.setAnimationDuration(5)
let random: CGFloat = CGFloat(Int(arc4random()) % 4)
let transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_4) * random)
animationLayer?.setAffineTransform(transform)
CATransaction.commit()
CATransaction.commit()
外層是移動(dòng)的效果,內(nèi)層是旋轉(zhuǎn)效果碧注,內(nèi)層的效果會(huì)先被提交執(zhí)行嚣伐。
仔細(xì)觀察動(dòng)畫效果,肉眼發(fā)現(xiàn)旋轉(zhuǎn)和移動(dòng)幾乎是同時(shí)開始的萍丐,它們分明是按順序提交的兩段代碼轩端,為什么會(huì)同時(shí)執(zhí)行?
這時(shí)候就需要了解下 CALayer 動(dòng)畫的形成原理逝变,在 CALayer 的頭文件里面有兩個(gè)方法基茵,modelLayer()
和 presentationLayer()
它們分別叫模型樹和呈現(xiàn)樹奋构,文檔已有詳細(xì)說明。
當(dāng)我們開始動(dòng)畫的時(shí)候拱层,我們必須要先確定動(dòng)畫最終效果是什么弥臼,在給某個(gè)動(dòng)畫屬性賦新值的時(shí)候,它是瞬間被修改的根灯,我們從模型樹中讀取径缅,它的值已經(jīng)是新的值,而在動(dòng)畫過程中的值由呈現(xiàn)樹來保存烙肺。但實(shí)際上 CA 內(nèi)部還有私有的渲染樹 CARender纳猪,渲染當(dāng)前的動(dòng)畫效果,并且是異步的茬高,所以上面兩次提交的動(dòng)畫兆旬,是分別在不同的線程中執(zhí)行的,并且不會(huì)阻塞主線程怎栽。
UIView 是如何禁用隱式動(dòng)畫的
UIView 的內(nèi)容是由 CALayer 呈現(xiàn)的丽猬,但在修改 UIView 屬性的時(shí)候,卻沒有動(dòng)畫效果熏瞄,說明 UIView 在包裝 CALayer 的時(shí)脚祟,對(duì)它做了手腳。
CALayer 屬性的修改的動(dòng)畫被叫做 action强饮,只要實(shí)現(xiàn) CAAction 協(xié)議的類由桌,都可以被作用于 CALayer 的動(dòng)畫。當(dāng) CALayer 的屬性被修改時(shí)邮丰,首先會(huì)調(diào)用 -actionForKey:
方法行您,傳遞修改的屬性,接著會(huì)通過4種方式來獲取動(dòng)畫的 action:
- CALayer 是否存在代理剪廉,并實(shí)現(xiàn)
-actionForlayer:forKey
方法娃循。 - 如果代理不存在或者未實(shí)現(xiàn)上述代理方法,會(huì)檢查 actions 字典斗蒋,是否包含所修改屬性的動(dòng)作捌斧。
- 如果
actions
字典,不包含當(dāng)前修改的屬性泉沾,會(huì)在繼承體系中查找 style 字典屬性捞蚂。 - 最后如果在
style
屬性里也沒法發(fā)現(xiàn),就會(huì)使用默認(rèn)的-defaultActionForKey:
跷究,也就是形成隱式動(dòng)畫的動(dòng)作姓迅。
然而,UIView 禁用隱式動(dòng)畫的方式非常簡(jiǎn)單,它遵循了 CALayerDelegate 并給 方法返回 nil
队贱,就不會(huì)有動(dòng)畫效果了色冀。
等等,那如果直接返回 nil柱嫌,那 UIView 不就做不了動(dòng)畫了嗎锋恬?animateWithDuration 這類 block 動(dòng)畫如何實(shí)現(xiàn)呢?比如修改顏色的動(dòng)畫效果:
UIView.animate(withDuration: 1, delay: 10, options: .curveEaseIn, animations: {
self.view.backgroundColor = UIColor.black
}, completion: nil)
雖然傳遞 delay 10 秒编丘,但 block 是瞬間執(zhí)行的与学,只不過動(dòng)畫的效果會(huì)在 10 秒之后才發(fā)生。上述 block 等價(jià)于
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDelay(10)
UIView.setAnimationDuration(1)
self.view.backgroundColor = UIColor.black
UIView.commitAnimations()
處于 beginAnimations
和 commitAnimations
之間的屬性修改會(huì)被做標(biāo)記 嘉抓,在 -actionForlayer:forKey
方法中索守,會(huì)為有標(biāo)記的操作返回特定的 CAAction 而不是 nil
,UIView 就可以自如的控制是否需要?jiǎng)赢嬓Ч恕?/p>
本文絕大部分內(nèi)容 CALayer 頭文件就有說明抑片,但是如果僅僅看頭文件的注釋卵佛,會(huì)不知所云,稍微了解一些用法敞斋,會(huì)清楚不少截汪。