Core Animation 第七章 隱式動畫

本章開始將正式進入動畫的部分三痰,首先要介紹的是隱式動畫。所謂隱式動畫就是由系統(tǒng)自動完成的動畫窜管。

事務(wù)

Core Animation基于一個假設(shè),說屏幕上的任何東西都可以(或者可能)做動畫散劫。 動畫并不需要你在Core Animation中手動打開,相反需要明確地關(guān)閉,否則他會一 直存在。例如下面的例子幕帆,在改變CALayer背景色的時候它會自己從舊值平滑的過渡到新值蜓肆。

class TransactionViewController: UIViewController {
    @IBOutlet weak var layerView: UIView!
    weak var colorLayer: CALayer!
    override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CALayer()
        self.layerView.layer.addSublayer(layer)
        layer.frame = CGRect(x: 35, y: 20, width: 180, height: 180)
        layer.backgroundColor = UIColor.blue.cgColor;
        colorLayer = layer
    }
    
    @IBAction func changeBtnClick(_ sender: UIButton) {
        let red = CGFloat(arc4random() % 256) / 255.0
        let green = CGFloat(arc4random() % 256) / 255.0
        let blue = CGFloat(arc4random() % 256) / 255.0
        colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
    }
}
默認(rèn)顏色漸變

這個就是一個隱式動畫症概,我們沒有指定動畫類型诅蝶,只是改變了一個屬性调炬,然后由系統(tǒng)來自動完成動畫效果缰泡。
那么這些又跟事務(wù)有什么關(guān)系呢棘钞?iOS中的事務(wù)可以理解為一系列動畫的集合宜猜,任何用指定事 務(wù)去改變可以做動畫的圖層屬性都不會立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時候 開始用一個動畫過渡到新值姨拥。

事務(wù)是通過 CATransaction 類來做管理叫乌。CATransaction 是在圖層樹改變的時候在一個沒有活躍事務(wù)的線程中由CoreAnimation自動創(chuàng)建的综芥,并且在run-loop重復(fù)的時候自動提交膀藐。CATransaction 沒有屬性或者實例方法,不需要使用者來單獨創(chuàng)建额各。但是可以用 begin()commit() 分別來入椣豪玻或者出棧蝇闭。

任何可以做動畫的圖層屬性都會被添加到棧頂?shù)氖聞?wù),你可以通
setAnimationDuration(_:) 來設(shè)置或者通過 animationDuration() 來獲取 當(dāng)前動畫的時間(默認(rèn)值為0.25)

接下來我們來讓上面例子中顏色的改變慢一點呻引。

@IBAction func changeBtnClick(_ sender: UIButton) {
      //開始事務(wù)
        CATransaction.begin()
        defer {
            //提交事務(wù)
            CATransaction.commit()
        }
        CATransaction.setAnimationDuration(1.0)
        let red = CGFloat(arc4random() % 256) / 255.0
        let green = CGFloat(arc4random() % 256) / 255.0
        let blue = CGFloat(arc4random() % 256) / 255.0
        colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
    }
使用事務(wù)來控制動畫的時間

當(dāng)然UIView中也有對應(yīng)的方法 beginAnimations(_ : , context: ) 逻悠, commitAnimations() 等方法童谒。當(dāng)然最常用的還是 UIView中基于閉包的動畫方法:animate(withDuration: , animations:)

完成塊

上面提到了 UIView 中常用的使用閉包來處理動畫的方式饥伊,而且還有有一個 completion 的閉包來表示動畫已經(jīng)執(zhí)行結(jié)束(完成塊)撵渡。CATranscation 中也有對應(yīng)的方法 setCompletionBlock() 作為動畫完成后的回調(diào)。下面我們來讓方塊每次改變顏色后都旋轉(zhuǎn)90°越除。

@IBAction func changeBtnClick(_ sender: UIButton) {
        //開始事務(wù)
        CATransaction.begin()
        defer {
            //提交事務(wù)
            CATransaction.commit()
        }
        CATransaction.setAnimationDuration(1.0)
        CATransaction.setCompletionBlock { 
            var transform = self.colorLayer.affineTransform()
            transform = transform.rotated(by: CGFloat(M_PI_2))
            self.colorLayer.setAffineTransform(transform)
        }
        let red = CGFloat(arc4random() % 256) / 255.0
        let green = CGFloat(arc4random() % 256) / 255.0
        let blue = CGFloat(arc4random() % 256) / 255.0
        colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
    }
使用完成塊旋轉(zhuǎn)圖層

圖層行為

處于好奇摘盆,你可能會嘗試打開事務(wù)之后直接對視圖孩擂,也就是對UIView設(shè)置背景顏色类垦。然后你就會發(fā)現(xiàn)視圖的背景顏色是瞬間改變的蚤认,并沒有漸變的過程砰琢。原因也很簡單训唱,隱式動畫被UIView禁用掉了挚冤。
之前也提到過你辣,UIView顯示的內(nèi)容實際都是它內(nèi)部的關(guān)聯(lián)圖層上的內(nèi)容舍哄,那么UIView是怎么禁用掉內(nèi)部圖層的隱式動畫的呢表悬?當(dāng)我們改變圖層屬性的時候蟆沫,它會調(diào)用 action(forKey:) 方法戒悠。關(guān)于 action(forKey:) 的調(diào)用我們可以在CALayer的頭文件中找到說明:

  • 首先查看代理是否實現(xiàn)了 action(for layer: CALayer, forKey event: String) 如果實現(xiàn)了則調(diào)用并返回結(jié)果
  • 如果沒有代理或者代理未實現(xiàn)該方法绸狐,那么圖層接著會檢查包含屬性名稱對應(yīng)行為的 action 字典
  • 如果 action中沒有包含的屬性寒矿,那么會繼續(xù)在圖層的style字典中查找屬性名
  • 如果 style 中也沒有查找到的話符相,圖層會直接調(diào)用 defaultActionForKey()方法啊终,返回一個標(biāo)準(zhǔn)行為傲须。

結(jié)果也就很明顯了,UIView為繼承了代理搞旭,并且如果當(dāng)前View不在動畫塊內(nèi)的話肄渗,action(forKey:) 方法就返回nil翎嫡。

override func viewDidLoad() {
        super.viewDidLoad()
        print("Outside: \(layerView .action(for: self.layerView.layer, forKey: "backgroundColor"))")
        UIView.beginAnimations(nil, context: nil)
        print("Inside: \(layerView .action(for: self.layerView.layer, forKey: "backgroundColor"))")
        UIView.commitAnimations();
    }
視圖開啟事務(wù)與未開啟事務(wù) actionForKey 結(jié)果對比

當(dāng)然我們也可以通過 CATransaction 來禁用動畫,在 CATransaction.begin() 之后調(diào)用

CATransaction.setDisableActions(true)

鋪墊了這么多具伍,接下來我們嘗試通過為colorLayer定義一個action字典來實現(xiàn)自定義圖層行為人芽,例如我們希望新的顏色不是漸變的萤厅,而是從左側(cè)劃入的惕味。

class TransactionViewController: UIViewController {
    @IBOutlet weak var layerView: UIView!
    weak var colorLayer: CALayer!
    override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CALayer()
        self.layerView.layer.addSublayer(layer)
        layer.frame = CGRect(x: 35, y: 20, width: 180, height: 180)
        layer.backgroundColor = UIColor.clear.cgColor;
        let transition = CATransition()
        transition.type = kCATransitionPush
        transition.subtype = kCATransitionFromLeft
        layer.actions = ["backgroundColor": transition]
        colorLayer = layer
    }
    
    @IBAction func changeBtnClick(_ sender: UIButton) {
        //開始事務(wù)
        CATransaction.begin()
        defer {
            //提交事務(wù)
            CATransaction.commit()
        }
        CATransaction.setAnimationDuration(1.0)
        CATransaction.setCompletionBlock { 
            var transform = self.colorLayer.affineTransform()
            transform = transform.rotated(by: CGFloat(M_PI_2))
            self.colorLayer.setAffineTransform(transform)
        }
        let red = CGFloat(arc4random() % 256) / 255.0
        let green = CGFloat(arc4random() % 256) / 255.0
        let blue = CGFloat(arc4random() % 256) / 255.0
        colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
    }
}
自定義圖層背景顏色行為

呈現(xiàn)(presentation)與模型(model)

這部分內(nèi)容比較繁瑣主守,我就只是引用原文的內(nèi)容了丸逸,不過簡單來講就是我們看到的動畫(presentation)與我們實際操作的圖層(model)時兩個圖層。
CALayer的屬性行為其實很不正常憔维,因為改變一個圖層的屬性并沒有立刻生效畏邢,而是通過一段時間漸變更新舒萎。這是怎么做到的呢?
當(dāng)你改變一個圖層的屬性摊灭,屬性值的確是立刻更新的(如果你讀取它的數(shù)據(jù)败徊,你會發(fā)現(xiàn)它的值在你設(shè)置它的那一刻就已經(jīng)生效了)煤杀,但是屏幕上并沒有馬上發(fā)生改變沈自。這是因為你設(shè)置的屬性并沒有直接調(diào)整圖層的外觀酥泛,相反柔袁,他只是定義了圖層動畫結(jié)束之后將要變化的外觀捶索。
當(dāng)設(shè)置 CALayer 的屬性,實際上是在定義當(dāng)前事務(wù)結(jié)束之后圖層如何顯示的模型燎竖。Core Animation 扮演了一個控制器的角色构回,并且負(fù)責(zé)根據(jù)圖層行為和事務(wù)設(shè)置去不斷更新視圖的這些屬性在屏幕上的狀態(tài)纤掸。
我們討論的就是一個典型的微型 MVC 模式。CALayer 是一個連接用戶界面(就是 MVC 中的 view )虛構(gòu)的類掏愁,但是在界面本身這個場景下,CALayer 的行為更像是存儲了視圖如何顯示和動畫的數(shù)據(jù)模型印蓖。實際上,在蘋果自己的文檔中他宛,圖層樹通常都是值的圖層樹模型。
iOS中欠气,屏幕每秒鐘重繪60次厅各。如果動畫時長比60分之一秒要長, Core Animation 就需要在設(shè)置一次新值和新值生效之間预柒,對屏幕上的圖層進行重新組織队塘。這意味著 CALayer 除了“真實”值(就是你設(shè)置的值)之外,必須要知道當(dāng)前顯示在屏幕上的屬性值的記錄宜鸯。
每個圖層屬性的顯示值都被存儲在一個叫做呈現(xiàn)圖層的獨立圖層當(dāng)中憔古,他可以通過 -presentationLayer 方法來訪問。這個呈現(xiàn)圖層實際上是模型圖層的復(fù)制淋袖,但是它的屬性值代表了在任何指定時刻當(dāng)前外觀效果鸿市。換句話說焰情,你可以通過呈現(xiàn)圖層的值來獲取當(dāng)前屏幕上真正顯示出來的值初橘。
我們在第一章中提到除了圖層樹展东,另外還有呈現(xiàn)樹。呈現(xiàn)樹通過圖層樹中所有圖層的呈現(xiàn)圖層所形成。注意呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時候創(chuàng)建,所以在那之前調(diào)用 -presentationLayer 將會返回nil。
你可能注意到有一個叫做 –modelLayer 的方法屹培。在呈現(xiàn)圖層上調(diào)用 –modelLayer 將會返回它正在呈現(xiàn)所依賴的 CALayer 溜歪。通常在一個圖層上調(diào)用 -modelLayer 會返回 self(實際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)自阱。

大多數(shù)情況下叫确,你不需要直接訪問呈現(xiàn)圖層次乓,你可以通過和模型圖層的交互,來讓 Core Animation 更新顯示。兩種情況下呈現(xiàn)圖層會變得很有用,一個是同步動畫,一個是處理用戶交互肖抱。

  • 如果你在實現(xiàn)一個基于定時器的動畫荤崇,而不僅僅是基于事務(wù)的動畫每篷,這個時候準(zhǔn)確地知道在某一時刻圖層顯示在什么位置就會對正確擺放圖層很有用了仑嗅。
  • 如果你想讓你做動畫的圖層響應(yīng)用戶輸入,你可以使用-hitTest:方法來判斷指定圖層是否被觸摸涯保,這時候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會顯得更有意義及志,因為呈現(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置冶共,而不是當(dāng)前動畫結(jié)束之后的位置谐岁。

接下來的例子中塞祈,點擊屏幕上的任意位置將會讓圖層平移到那里坯临。點擊圖層本身可以隨機改變它的顏色辟宗。我們通過對呈現(xiàn)圖層調(diào)用 -hitTest: 來判斷是否被點擊。
如果修改代碼讓 -hitTest: 直接作用于 colorLayer 而不是呈現(xiàn)圖層芥丧,你會發(fā)現(xiàn)當(dāng)圖層移動的時候它并不能正確顯示坊罢。這時候你就需要點擊圖層將要移動到的位置而不是圖層本身來響應(yīng)點擊(這就是為什么用呈現(xiàn)圖層來響應(yīng)交互的原因)。

class HitTestViewController: UIViewController {
    weak var colorLayer: CALayer!
    override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CALayer()
        view.layer.addSublayer(layer)
        layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer.position = view.layer.position
        layer.backgroundColor = UIColor.red.cgColor
        colorLayer = layer
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let point = touches.first?.location(in: view)
        if self.colorLayer.presentation()?.hitTest(point!) != nil {
            let red = CGFloat(arc4random() % 256) / 255.0
            let green = CGFloat(arc4random() % 256) / 255.0
            let blue = CGFloat(arc4random() % 256) / 255.0
            colorLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor
        } else {
            CATransaction.begin()
            CATransaction.setAnimationDuration(4.0)
            colorLayer.position = point!
            CATransaction.commit()
        }
    }
}

總結(jié)

本章主要介紹了 Core Animation 的隱式動畫已經(jīng)背后實現(xiàn)的原理始衅。

往期回顧:

序章
第一章 - 圖層樹
第二章 - 寄宿圖
第三章 - 圖層幾何
第四章 - 視覺效果
第五章 - 變換
第六章 專用圖層(上)
第六章 專用圖層(下)
項目中使用的代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末类茂,一起剝皮案震驚了整個濱河市迟螺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舍咖,老刑警劉巖煮仇,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贸桶,死亡現(xiàn)場離奇詭異蚀瘸,居然都是意外死亡安接,警方通過查閱死者的電腦和手機滴某,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門墓拜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咬摇,你說我怎么就攤上這事儡陨。” “怎么了辙售?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵轻抱,是天一觀的道長。 經(jīng)常有香客問我旦部,道長祈搜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任士八,我火速辦了婚禮容燕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婚度。我一直安慰自己蘸秘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布蝗茁。 她就那樣靜靜地躺著醋虏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哮翘。 梳的紋絲不亂的頭發(fā)上颈嚼,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音饭寺,去河邊找鬼粘舟。 笑死,一個胖子當(dāng)著我的面吹牛柑肴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旬薯,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硕舆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骤公,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阶捆,沒想到半個月后钦听,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡倍奢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年朴上,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卒煞。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡痪宰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畔裕,到底是詐尸還是另有隱情衣撬,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布扮饶,位于F島的核電站具练,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贴届。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一蜡吧、第九天 我趴在偏房一處隱蔽的房頂上張望毫蚓。 院中可真熱鬧,春花似錦昔善、人聲如沸元潘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翩概。三九已至,卻和暖如春返咱,著一層夾襖步出監(jiān)牢的瞬間钥庇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工咖摹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留评姨,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓萤晴,卻偏偏與公主長得像吐句,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子店读,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容