第四章 - Animation

動(dòng)畫(Animation) 是屬性隨著時(shí)間的可變變化涮雷。變化中的屬性可以是位置的: 物體移動(dòng)或者改變尺寸源内。但是其它種類的屬性也能 Animate。例如, 視圖的背景色能從紅色變?yōu)榫G色, 不是馬上的, 但是能感覺(jué)到從一種顏色漸變到另外一種顏色∧さ觯或者一個(gè)視圖可以從不透明變?yōu)橥该? 不是立即的, 但是肉眼可以看到褪色嗽交。

動(dòng)畫花費(fèi)很多計(jì)算和時(shí)間, 幸運(yùn)的是你不是自己來(lái)執(zhí)行動(dòng)畫: 你描述它, 整理它, 它會(huì)為你執(zhí)行。你根據(jù)需要來(lái)獲取動(dòng)畫颂斜。

要求一個(gè)動(dòng)畫能夠像設(shè)置屬性值那樣簡(jiǎn)單; 有時(shí)一行代碼就能設(shè)置動(dòng)畫:

myLayer.backgroundColor = UIColor.redColor().CGColor // animate to red
img

選擇模擬器的 Debug -> Slow Animations 菜單可以讓動(dòng)畫運(yùn)行的更慢以幫助你檢測(cè)動(dòng)畫的執(zhí)行夫壁。

Drawing, Animation, and Threading


?

當(dāng)你改變一個(gè)可見(jiàn)的視圖屬性, 那個(gè)改變不會(huì)立即可見(jiàn)。而是, 系統(tǒng)記錄你想做的這個(gè)變化(change), 并把這個(gè)視圖標(biāo)記成需要重繪(redrawn)沃疮。然后, 當(dāng)你的所有代碼都完成而且系統(tǒng)有空閑時(shí), 那么它就會(huì)重繪所有需要重繪的視圖, 應(yīng)用它們新的可見(jiàn)屬性外貌(features)盒让。我們把這叫做重繪時(shí)刻

// 視圖從綠色開始
view.backgroundColor = UIColor.redColor()
// 耗時(shí)代碼放在這兒
view.backgroundColor = UIColor.greenColor()
// 代碼結(jié)束, 重繪時(shí)刻

系統(tǒng)累計(jì)所有想要的變化直到重繪時(shí)刻的到來(lái), 并且重繪時(shí)刻不會(huì)發(fā)生直到你的代碼完成了, 所以, 當(dāng)重繪時(shí)刻確實(shí)發(fā)生了, 視圖中最后一個(gè)積累的顏色變?yōu)榫G色 — 它已經(jīng)是它的 color 了司蔬。因此, 不管顏色變化之間有多少耗時(shí)代碼, 用戶一點(diǎn)也看不到任何顏色變化邑茄。

動(dòng)畫的工作原理也是這樣, 處理過(guò)程也部分相同。當(dāng)你要執(zhí)行一個(gè)動(dòng)畫時(shí), 動(dòng)畫不會(huì)出現(xiàn)在屏幕上直到下一個(gè)重繪時(shí)刻來(lái)臨俊啼。(你可以強(qiáng)制動(dòng)畫立刻開始, 但這不常用)肺缕。

動(dòng)畫就像一種電影和卡通。所以, 當(dāng)你讓一個(gè)視圖從位置 1 動(dòng)畫到位置 2, 通常有一個(gè)事件序列:

  1. 重新配置視圖授帕。該視圖現(xiàn)在被放置在位置 2, 但是還沒(méi)到重繪時(shí)刻, 所以它仍然顯示在位置 1 處同木。
  2. 為視圖指定一個(gè)從位置 1 到位置 2的動(dòng)畫。
  3. 等剩下的代碼執(zhí)行完畢
  4. 現(xiàn)在到重繪的時(shí)刻了跛十。如果沒(méi)有動(dòng)畫, 那么該視圖現(xiàn)在就會(huì)突然顯示在位置 2 上彤路。 但是如果有動(dòng)畫, 那么"動(dòng)畫電影" 就會(huì)出現(xiàn)。它從位置 1 開始展示視圖, 所以那仍舊是用戶所看到的芥映。
  5. 動(dòng)畫繼續(xù), 然后在位置 1 和位置 2 中間的位置顯示視圖洲尊。
  6. 動(dòng)畫停止, 在位置 2 處顯示結(jié)束視圖
  7. "動(dòng)畫電影"被移除, 視圖顯示在位置 2 處 — 在第一步放置它的地方。

了解到"動(dòng)畫電影"和真實(shí)視圖上所發(fā)生的是不同的東西是正確配置動(dòng)畫的關(guān)鍵奈偏。 新手經(jīng)常抱怨的一個(gè)地方就是動(dòng)畫會(huì)發(fā)生跳躍, 就是動(dòng)畫執(zhí)行的最后會(huì)跳到另外一個(gè)位置, 這跟他們的期望不符颊郎。發(fā)生這個(gè)的原因是視圖沒(méi)有在"動(dòng)畫電影"中匹配它最后的位置; "跳躍" 的發(fā)生是因?yàn)橐晥D顯示的實(shí)際位置和"電影"的最后一幀不匹配。

屏幕前面實(shí)際上沒(méi)有"動(dòng)畫電影" — 實(shí)際上, 并不是圖層(layer)自身顯示到屏幕上; 它是派生出來(lái)的一個(gè)叫做顯示層(presentation layer)霎苗。因此, 當(dāng)你從位置 1 到位置 2 animate 視圖或圖層的位置的位置變化時(shí), 它名義上的位置會(huì)立即變化; 同時(shí), 顯示層的位置仍舊保持不變直到重繪時(shí)刻, 然后會(huì)隨著時(shí)間變化, 因?yàn)槟鞘瞧聊簧蠈?shí)際上所繪制的, 它也是用戶所看到的。

(圖層的顯示層可以通過(guò)它的 presentionLayer 方法訪問(wèn) — 并且圖層自身可以通過(guò)顯示圖層的 modeLayer 方法來(lái)訪問(wèn), 在本章和下一章?中, 我們也將?訪問(wèn)顯示層)榛做。

就像真實(shí)的電影(特別是老式的卡通動(dòng)畫), "動(dòng)畫電影" 擁有 "幀"(frames)唁盏。Animated 值不會(huì)平滑和連續(xù)地變化; 它是以很小和獨(dú)立的增長(zhǎng)變化來(lái)給我們平滑連續(xù)變化的錯(cuò)覺(jué)。這種錯(cuò)覺(jué)有效, 因?yàn)樵O(shè)備自身也經(jīng)受著周期性的迅速的, 或多或少的常規(guī)屏幕刷新, 并且不斷增加的變化是在這些刷新之間平息的检眯。Apple 調(diào)用系統(tǒng)組件來(lái)負(fù)責(zé)這種 "animation server"厘擂。

animation server 在單獨(dú)的線程上操作。你不必?fù)?dān)心細(xì)節(jié), 但是你也不能忽略它锰瘸。代碼獨(dú)立運(yùn)行也可能和動(dòng)畫同時(shí)運(yùn)行刽严。 — 那就是多線程的意義 — 所以在動(dòng)畫和代碼之間的溝通需要某些計(jì)劃。

動(dòng)畫可以用來(lái)執(zhí)行某些清理工作避凝。其中之一就是對(duì)觸摸(touches)的處理: 當(dāng)動(dòng)畫在飛行時(shí)(in-flight), 如果代碼沒(méi)有在運(yùn)行, 那么界面默認(rèn)響應(yīng)用戶的觸摸, 這會(huì)導(dǎo)致各種破壞, 因?yàn)橐晥D會(huì)嘗試響應(yīng)觸摸而動(dòng)畫仍舊在發(fā)生并且屏幕顯示不匹配實(shí)際舞萄。常用的處理方式就是當(dāng)你為視圖設(shè)置了動(dòng)畫, 那就關(guān)閉該視圖的對(duì)觸摸的響應(yīng), 并在動(dòng)畫結(jié)束時(shí)把它改回來(lái)眨补。

因?yàn)榇a可以在設(shè)置動(dòng)畫之后運(yùn)行或者在動(dòng)畫飛行過(guò)程中開始運(yùn)行, 你需要關(guān)心創(chuàng)建有沖突的動(dòng)畫問(wèn)題。多個(gè)動(dòng)畫可以被同時(shí)設(shè)置(和執(zhí)行)倒脓。

不可抗力可能會(huì)打斷動(dòng)畫撑螺。用戶可能按下 Home 鍵讓 app 進(jìn)入后臺(tái)或者在動(dòng)畫飛行的時(shí)候來(lái)電話。在 app 進(jìn)入后臺(tái)時(shí), 系統(tǒng)會(huì)取消正在飛行中的動(dòng)畫崎弃。當(dāng) app 恢復(fù)后, 會(huì)顯示動(dòng)畫最后的狀態(tài)甘晤。但是如果你想把動(dòng)畫恢復(fù)到它離開的那個(gè)狀態(tài), 你需要一些精明的代碼。

Image View and Image Animation


?

UIImageView 提供一個(gè) UIImages 數(shù)組, 作為它的 animationImageshighlitedAnimationImages 屬性的值饲做。這個(gè)數(shù)組代表著簡(jiǎn)單卡通的幀(frames); 當(dāng)你發(fā)送 startAnimating 消息, 圖片就會(huì)以 animationDuration 屬性所定義的幀率輪流顯示, 重復(fù)由 animationRepeatCount 屬性指定的次數(shù)线婚。(默認(rèn)為 0, 意思是一直重復(fù)), 或者直到收到 stopAnimating 消息。在動(dòng)畫之前和之后, 該 image view 持續(xù)顯示它的 image(或 highlightedImage)盆均。

例如, 我們想讓一個(gè)名為 Mars 的圖片出現(xiàn)在屏幕上的某個(gè)地方并閃爍 3 次塞弊。

    let mars = UIImage(named: "Mars")!

    // 打開圖形上下文
    UIGraphicsBeginImageContextWithOptions(mars.size, false, 0)
    // 獲取一個(gè)空白圖像
    let empty = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext() // 關(guān)閉圖形上下文

    let arr = [mars, empty, mars, empty, mars]
    let iv = UIImageView(image:empty) // 創(chuàng)建一個(gè)不含圖片的 UIImageView
    iv.frame.origin = CGPointMake(100,100)
    self.view.addSubview(iv) // 把 UIImageView 添加到 view 上
    iv.animationImages = arr // 設(shè)置 UIImageView 的圖片序列
    iv.animationDuration = 2
    iv.animationRepeatCount = 1
    iv.startAnimating() // 開始動(dòng)畫

UIImageView 動(dòng)畫可以和其它類型的動(dòng)畫組合使用。例如, 閃爍 Mars 圖片的同時(shí)向右滑動(dòng) UIImageView, 使用下一節(jié)描述的視圖動(dòng)畫缀踪。

image 本身也可以是 animated image居砖。多張圖片組成了一個(gè)序列作為簡(jiǎn)單卡通的"幀"(frames)。使用下面的其中之一的 UIImage 類方法來(lái)創(chuàng)建一個(gè) animated image:

  • animatedImageWithImages:duration:

? 就像 UIImageView 的 animationImages, 你提供一個(gè) UIImage 的數(shù)組驴娃。你還可以提供整個(gè)動(dòng)畫的持續(xù)時(shí)間奏候。

  • animatedImageNamed:duration:

    提供單個(gè)圖片文件的名字, 就像 init(named:) 那樣, 不帶文件后綴。運(yùn)行時(shí)會(huì)給你提供的圖片名追加 "0"(失敗則為 ”1“), 并把該圖片設(shè)置為動(dòng)畫序列中的第一張圖片唇敞。以此類推, 隨后追加的數(shù)字自增(直到用光圖片或達(dá)到 ”1024“)蔗草。

  • animatedResizableImageNamed:capInsets:resizingMode:duration:

    ? 把 animated image 和 resizable image 相結(jié)合

animated image 可以像 UIImage 那樣作為某個(gè)界面對(duì)象的屬性出現(xiàn)在界面中的任何地方。 下面這個(gè)例子, 我構(gòu)造了一個(gè)不同尺寸的紅色圓形序列, 然后我在 UIBUtton 中展示這個(gè)被 animated 后的圖形疆柔。

class ViewController: UIViewController {

    // 在 storyboard 中拖拽一個(gè) UIButton, 并和該 b 變量連線
    @IBOutlet var b: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 一個(gè)跳動(dòng)的紅點(diǎn)
        var arr = [UIImage]()
        let w : CGFloat = 18
        for i in 0 ..< 6 {
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(w,w), false, 0)
            let con = UIGraphicsGetCurrentContext()!
            CGContextSetFillColorWithColor(con, UIColor.redColor().CGColor)
            let ii = CGFloat(i)
            CGContextAddEllipseInRect(con, CGRectMake(0+ii,0+ii,w-ii*2,w-ii*2))
            CGContextFillPath(con)
            let im = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            arr += [im]
        }

        let im = UIImage.animatedImageWithImages(arr, duration:0.5)
        b.setImage(im, forState:.Normal) // b is a button in the interface
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

View Animation


?

所有的動(dòng)畫最終都是圖層動(dòng)畫(layer animation), 稍后我會(huì)在本章解釋咒精。然而, 對(duì)于有些屬性, 你可以直接對(duì) UIView 進(jìn)行?動(dòng)畫繪制: 這些屬性是 alphabounds旷档、center模叙、frametransform 還有(如果 那個(gè)view 沒(méi)有實(shí)現(xiàn) drawRect:) backgroundColor鞋屈。 你也可以對(duì) UIView 內(nèi)容的變化繪制動(dòng)畫范咨。

UIView 繪制動(dòng)畫的語(yǔ)法涉及調(diào)用一個(gè) UIView 類方法, 并傳遞一個(gè)用于繪制動(dòng)畫的閉包。 假設(shè)我們?cè)诮缑嬷杏幸粋€(gè) self.v 的 UIView, 背景為黑色, 我們現(xiàn)在把它變紅色:

UIView.animateWithDuration(0.4, animations: {
    self.v.backgroundColor = UIColor.redColor()
})

同時(shí)改變視圖的顏色和位置:

UIView.animateWithDuration(0.4, animations: {
    self.v.backgroundColor = UIColor.redColor()
    self.v.center.y += 100
})

我們還可以使用同一個(gè) animations: block 對(duì)多個(gè)視圖繪制動(dòng)畫變化厂庇。假設(shè)我們想讓一個(gè)視圖溶解到另外一個(gè)視圖中, 那么先讓第二個(gè)視圖顯示在視圖層級(jí)上, alpha 設(shè)置為 0, 以使它不可見(jiàn)渠啊。 然后我們把第一個(gè)視圖的 alpha 設(shè)置為 0, 第二個(gè)視圖的 alpha 設(shè)置為 1, 繪制動(dòng)畫。

那種情況下, 我們最好把視圖層級(jí)中的第二個(gè)視圖放在動(dòng)畫開始之前(不可見(jiàn), 因?yàn)樗?alpha 從 0 開始)并且在動(dòng)畫結(jié)束時(shí)(不可見(jiàn), 因?yàn)樗?alpha 以 0 結(jié)束)移除第一個(gè)視圖权旷。還有個(gè)額外的參數(shù) completion: 用來(lái)指定動(dòng)畫結(jié)束后應(yīng)該做點(diǎn)什么:

let v2 = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 50));
v2.alpha = 0;

self.v.superView!.addSubview(v2);
UIView.animateWithDuration(0.4, animations: {
    self.v.alpha = 0
    v2.alpha = 1
}, completion: {
    _ in
    self.v.removeFromSuperview()
})

現(xiàn)在界面中拖拽一個(gè) UIView, 然后在控制器中聲明一個(gè)名為 v , 繼承自 UIView 的變量, 將它和界面中的那個(gè)視圖連線替蛉。記住, **界面中的對(duì)象想要和控制器中的其它對(duì)象交互或者你需要用代碼控制界面中的對(duì)象, 你需要在控制器中為該對(duì)象創(chuàng)建 outlet, 例如

@IBOutlet var v: UIView!

img
另外一種從帶有動(dòng)畫的層級(jí)視圖中移除一個(gè)視圖的方法是調(diào)用 performSystemAnimation:onViews:options:animations:completion:, 第一個(gè)參數(shù)使用 。Delete(僅有的可用的第一個(gè)參數(shù))。這會(huì)讓那個(gè)視圖變模糊躲查、收縮和褪色, 之后再給它發(fā)送 removeFromSuperview 方法它浅。

performWithoutAnimation: 方法會(huì)執(zhí)行不會(huì)繪制動(dòng)畫的 block。下面這個(gè)偽造的例子中, 視圖跳到新的位置上并慢慢變紅:

UIView.animateWithDuration(0.4, animations: {
    self.v.backgroundColor = UIColor.redColor()
    UIView.performWithoutAnimation {
       self.v.center.y += 100
    }
})

如果你在繪制動(dòng)畫過(guò)程中改變了視圖的屬性, 那么之后不應(yīng)該再改變這個(gè)屬性, 否則結(jié)果會(huì)讓人迷惑熙含。下面這段代碼就不合邏輯:

UIView.animateWithDuration(2, animations: {
    self.v.center.y += 100
})
self.v.center.y += 300

實(shí)際發(fā)生的是這個(gè)視圖向下跳躍 300 點(diǎn), 然后向下繪制了 100 點(diǎn)的動(dòng)畫罚缕。那通常不是你想要的。你在 animations: block 中安排好將要被繪制動(dòng)畫的可動(dòng)畫視圖屬性之后, 不要再次更改視圖屬性的值直到動(dòng)畫執(zhí)行完之后怎静。

下面這段代碼用單個(gè)動(dòng)畫使視圖向下移動(dòng)了 400 點(diǎn):

UIView.animateWithDuration(2, animations: {
    self.v.center.y += 300
    self.v.center.y += 100
})

這是因?yàn)榛疚恢靡晥D動(dòng)畫默認(rèn)是可以疊加的(iOS 8 及以后)邮弹。 這意味著第二個(gè)動(dòng)畫和第一個(gè)動(dòng)畫是同時(shí)運(yùn)行的, 它倆混合在一塊兒。

img
在新的 iOS 9 中, UIVisualEffectView 和普通的 UIView 那樣也是可動(dòng)畫的蚓聘。還有, 設(shè)置 UIVisualEffectView 的 effect 也是可動(dòng)畫的! 例如, 你可以在 animations: block 中把 UIVisualEffectView 的 effect 屬性設(shè)置為 nil 然后設(shè)置它的 effect 屬性為 UIBlurEffect 來(lái)繪制 blur 動(dòng)畫腌乡。

View Animation Options


UIView 的類方法 animateWithDuration:animateWithDuration:completion: 都是退化

了的形式。完整的形式是 animateWithDuration:delay:options:animations:completion:

參數(shù)有:

  • duration

? 動(dòng)畫持續(xù)的時(shí)間: 動(dòng)畫從開始到完成花費(fèi)了多長(zhǎng)時(shí)間(以秒為單位 )夜牡。 你也可以把這個(gè)參數(shù)當(dāng)作動(dòng)畫的速度与纽。顯而易見(jiàn), 如果兩個(gè)視圖在同樣的時(shí)間移動(dòng)了不同的距離, 那么移動(dòng)的更遠(yuǎn)的那個(gè)視圖移動(dòng)的更快。

  • delay

? 動(dòng)畫開始之前延遲的時(shí)間塘装。默認(rèn)沒(méi)有延遲急迂。延遲和應(yīng)用到動(dòng)畫中使用的延遲執(zhí)行不同; 動(dòng)畫是立即應(yīng)用的, 但是當(dāng)它空轉(zhuǎn)的時(shí)候, 沒(méi)有可見(jiàn)的變化, 直到 delay 時(shí)間過(guò)去。

  • options

? 組合額外選項(xiàng)的位掩碼

  • animations

? 這個(gè) block 包含了要被繪制動(dòng)畫的視圖屬性的變化蹦肴。

  • completion

? 動(dòng)畫結(jié)束(或 nil)的時(shí)候運(yùn)行這個(gè) block僚碎。它接收一個(gè) Bool 值來(lái)標(biāo)示動(dòng)畫是否運(yùn)行結(jié)束了。這個(gè) block 含有一個(gè)?標(biāo)示 true 的參數(shù)以調(diào)用, 即使 animations: block 中沒(méi)有任何觸發(fā)動(dòng)畫的東西阴幌。這個(gè)塊兒還能進(jìn)一步指定動(dòng)畫, 結(jié)果就是鏈?zhǔn)絼?dòng)畫勺阐。

下面是某些簡(jiǎn)明的 options: 值(UIViewAnimationOptions), 你可能用的到:

Animation curve

? 動(dòng)畫曲線(animation curve)描述了在動(dòng)畫期間動(dòng)畫的變化速度。術(shù)語(yǔ) "ease" 的意思是在開始或結(jié)束時(shí)動(dòng)畫的中心速度和零速度之間是逐漸增加或減少的矛双。至多指定一個(gè):

  • .CurveEaseInOut(默認(rèn)的)
  • .CurveEaseIn
  • .CurveEaseOut
  • .CurveLinear(速度始終不變)

.Repeat

? 如果包含了這個(gè)參數(shù), 那么動(dòng)畫會(huì)無(wú)限重復(fù)渊抽。作為這個(gè)命令中的一部分, 沒(méi)有辦法去
? 指定一個(gè)明確的重復(fù)數(shù)字; 要么一直重復(fù), 要么一點(diǎn)兒不重復(fù)。這看起來(lái)像是疏忽;
? 我會(huì)在之后提供一個(gè)變通方案议忽。

.Autoreverse

? 如果包含了這個(gè)參數(shù), 那么動(dòng)畫會(huì)從開始運(yùn)行到結(jié)束(在給定的持續(xù)時(shí)間), 然后從結(jié)束運(yùn)行到開始 (也是在給定持續(xù)時(shí)間)懒闷。文檔中要求你只能在 repeat 不能接收時(shí)自動(dòng)反轉(zhuǎn); 你可以使用其中一個(gè)或都使用(或都不使用)。

當(dāng)使用 .Autoreverse 時(shí), 你會(huì)想在結(jié)尾進(jìn)行清除以至于當(dāng)動(dòng)畫結(jié)束時(shí)視圖能恢復(fù)到原來(lái)的位置:

let opts = UIViewAnimationOptions.Autoreverse
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
    self.v.center.x += 100
}, completion: nil)

這個(gè)視圖動(dòng)畫先向右移動(dòng) 100 點(diǎn)在向左移動(dòng) 100 點(diǎn)回到原來(lái)的位置, 最后跳回到右側(cè)100點(diǎn)的位置上栈幸。原因是我們賦值給視圖的 center 的 x 的最后實(shí)際值是 100 點(diǎn)向右, 所以當(dāng)動(dòng)畫結(jié)束時(shí), 視圖仍舊顯示 100 點(diǎn)向右愤估。解決辦法是在 completion: 處理中把視圖移動(dòng)回到它原來(lái)的位置上:

let opts  = UIViewAnimationOptions.Autoreverse
let xorig = self.v.center.x
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
    self.v.center.x += 100
}, completion: {
    _ in
    self.v.center.x = xorig
})

使用一個(gè)過(guò)時(shí)的語(yǔ)法來(lái)設(shè)置動(dòng)畫重復(fù)的次數(shù):

let count = 3
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
    UIView.setAnimationRepeatCount(Float(count))
    self.v.center.x += 100
}, completion: {
    _ in 
    self.v.center.x = xorig
})

最好添加一個(gè)重復(fù)次數(shù)的參數(shù), 在附錄 B 中我會(huì)用一個(gè)類方法來(lái)擴(kuò)展 UIView 給它添加一個(gè)重復(fù)次數(shù)的參數(shù)。

還有一些選項(xiàng)說(shuō)明了如果另外一個(gè)動(dòng)畫已經(jīng)指定(ordered)或正在飛行中(in-flight)會(huì)發(fā)生什么:

.BeginFromCurrentState

? 如果這個(gè)動(dòng)畫 animates 了一個(gè)之前已經(jīng)指定好或in-flight 的動(dòng)畫 animated 過(guò)的屬性, 那么不是取消之前的動(dòng)畫(立即完成所請(qǐng)求的變化), 如果它正常發(fā)生, 這個(gè)動(dòng)畫會(huì)使用顯示層來(lái)決定從哪兒開始, 并且, 如果可能的話, 它會(huì)把它的動(dòng)畫和之前的動(dòng)畫混合在一塊兒侦镇。

.OverrideInheritedDuration

? 阻止從周邊或運(yùn)動(dòng)中(in-flight)的動(dòng)畫繼承持續(xù)時(shí)間(duration), 默認(rèn)是繼承的。

.OverrideInheritedCurve

? 阻止從周邊或運(yùn)動(dòng)中(in-flight)的動(dòng)畫繼承動(dòng)畫曲線(curve), 默認(rèn)是繼承的织阅。

iOS 7 及之前你或許會(huì)用到少量的 .BeginFromCurrentState, 因?yàn)?iOS 8 之后, 簡(jiǎn)單的視圖動(dòng)畫默認(rèn)是可疊加的壳繁。例如, 下面這段代碼在 iOS 7 中如果你不使用 .BeginFromCurrentState 的話就會(huì)發(fā)生跳躍(因?yàn)槲覀冋付▋蓚€(gè)有沖突的視圖位置的動(dòng)畫), 但是在 iOS 8 及之后就是單個(gè)流暢的對(duì)角線動(dòng)畫:

UIView.animatedWithDuration(1, animations: {
    self.v.center.x += 100
})
UIView.animatedWithDuration(1, animations: {
    self.v.center.y += 100
})

為了看動(dòng)畫疊加的意思是什么, 嘗試下面這段代碼:

UIView.animateWithDuration(2, animations: {
    self.v.center.x += 100
})

delay(1) {
let opts = UIViewAnimationOptions.BeginFromCurrentState
UIView.animateWithDuration(1, delay: 0, options: opts,
    animations: {
        self.v.center.y += 100
    }, completion: nil)
}

Canceling a View Animation


視圖動(dòng)畫正在運(yùn)行時(shí)你怎么取消它? 為了方便解釋, 我把視圖的原始位置和最終位置保存到了屬性中:

self.pOrig    = self.v.center
self.pFinal   = self.v.center
self.pFinal.x += 100

UIView.animateWithDuration(4, animations: {
    self.v.center = self.pFinal
})

現(xiàn)在假想在動(dòng)畫執(zhí)行期間有一個(gè)按鈕, 按下按鈕會(huì)取消動(dòng)畫。 我們?cè)趺醋龅?

一種可能是我們到 CALayer 層并調(diào)用 removeAllAnimations 方法。(如果那個(gè) layer 擁有不止一個(gè)動(dòng)畫, 而你只想移除它們中的一個(gè), 你可以調(diào)用 removeAnimationForKey:方法; 我會(huì)在本章的后面談?wù)撊绾瓮ㄟ^(guò) key 來(lái)區(qū)分 layer 動(dòng)畫)闹炉。移除所有的動(dòng)畫實(shí)在是簡(jiǎn)明扼要, 但是缺點(diǎn)是它簡(jiǎn)單地讓動(dòng)畫徹底停止了, 視圖會(huì)跳轉(zhuǎn)到它最后被設(shè)定的位置上, 和 app 進(jìn)入后臺(tái)系統(tǒng)自動(dòng)調(diào)用的一樣:

self.v.layer.removeAllAnimations()

如果我們使用 removeAllAnimations 那么視圖就會(huì)跳到它最后的位置上; 我們想讓它在此刻保持在當(dāng)前位置上 — 即動(dòng)畫的當(dāng)前位置蒿赢。那個(gè)位置是當(dāng)前顯示層所在的位置。因此我們重新配置位于它的顯示層的位置的視圖, 然后移除那個(gè)動(dòng)畫, 再然后執(zhí)行最后的"趕快回家"動(dòng)畫:

    // unrecognized selector sent to instance 0x7fc288eaf160'
    // 要把 Selector 中的方法放在 viewDidLoad 這個(gè)方法的外面,還要注意方法后不能有冒號(hào)(如果方法中不需要傳遞按鈕本身這個(gè)參數(shù))
    func cancelAnimate() {
        self.v.layer.position = (self.v.layer.presentationLayer() as! CALayer).position
        self.v.layer.removeAllAnimations()
        UIView.animateWithDuration(0.1, animations: {
            self.v.center = self.pFinal
        })
    }

如果取消動(dòng)畫意味著視圖回到原來(lái)的位置, 那么把視圖的 center 設(shè)置為 self.pOrig 而非 self.pFinal渣触。如果你想把動(dòng)畫停在當(dāng)前它運(yùn)行到的位置上, 那么就省略最后一個(gè)動(dòng)畫羡棵。(即 0.1 的那個(gè))

如果我們要取消的動(dòng)畫是無(wú)限重復(fù)自動(dòng)反轉(zhuǎn)的呢:

self.pOrig = self.v.center
let opts: UIViewAnimationOptions = [.Autoreverse, .Repeat]
UIView.animateWithDuration(1, delay: 0, options: opts,
    animations: {
        self.v.center.x += 100
    }, completion: nil
)

那種情況下顯示另外一個(gè)動(dòng)畫足夠了, 因?yàn)樾碌膭?dòng)畫不會(huì)和第一個(gè)動(dòng)畫疊加。只有簡(jiǎn)單視圖動(dòng)畫是可疊加的, 什么是簡(jiǎn)單動(dòng)畫? 有一點(diǎn)必須是"非重復(fù)的"嗅钻。因此, 第二個(gè)動(dòng)畫取消了第一個(gè)動(dòng)畫皂冰。下面通過(guò)快速把它返回到它原來(lái)的位置上來(lái)取消第一個(gè)動(dòng)畫:

UIView.animateWithDuration(0.1, delay: 0, options: .BeginFromCurrentState,
    animations: {
        self.v.center = self.pOrig
    }, completion: nil)

這是 .BeginFromCurrentState 大顯身手的地方! 它阻止了視圖立即跳到右側(cè) 100點(diǎn)最后的位置, 而是把它設(shè)置為初始的重復(fù)動(dòng)畫。

Custom Animatable View Properties


你可以為自定義的視圖屬性繪制動(dòng)畫养篓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秃流,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柳弄,更是在濱河造成了極大的恐慌舶胀,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碧注,死亡現(xiàn)場(chǎng)離奇詭異嚣伐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)萍丐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門轩端,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人碉纺,你說(shuō)我怎么就攤上這事船万。” “怎么了骨田?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵耿导,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我态贤,道長(zhǎng)舱呻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任悠汽,我火速辦了婚禮箱吕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柿冲。我一直安慰自己茬高,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布假抄。 她就那樣靜靜地躺著怎栽,像睡著了一般丽猬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熏瞄,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天脚祟,我揣著相機(jī)與錄音,去河邊找鬼强饮。 笑死由桌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邮丰。 我是一名探鬼主播行您,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柠座!你這毒婦竟也來(lái)了邑雅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妈经,失蹤者是張志新(化名)和其女友劉穎淮野,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吹泡,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骤星,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爆哑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洞难。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揭朝,靈堂內(nèi)的尸體忽然破棺而出队贱,到底是詐尸還是另有隱情,我是刑警寧澤潭袱,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布柱嫌,位于F島的核電站,受9級(jí)特大地震影響屯换,放射性物質(zhì)發(fā)生泄漏编丘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一彤悔、第九天 我趴在偏房一處隱蔽的房頂上張望嘉抓。 院中可真熱鬧,春花似錦晕窑、人聲如沸抑片。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敞斋。三九已至级遭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渺尘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工说敏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸥跟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓盔沫,卻偏偏與公主長(zhǎng)得像医咨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子架诞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果拟淮,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌谴忧。在這里你可以看...
    每天刷兩次牙閱讀 8,488評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果很泊,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌沾谓。在這里你可以看...
    F麥子閱讀 5,110評(píng)論 5 13
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)委造、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,102評(píng)論 4 62
  • 書寫的很好均驶,翻譯的也棒昏兆!感謝譯者,感謝感謝妇穴! iOS-Core-Animation-Advanced-Techni...
    錢噓噓閱讀 2,297評(píng)論 0 6
  • 屏幕上下出現(xiàn)黑邊的問(wèn)題 解決辦法 1.新建Launch Screen.storyboard文件新建文件.png 2...
    CoderZb閱讀 1,732評(píng)論 0 1