動(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è)事件序列:
- 重新配置視圖授帕。該視圖現(xiàn)在被放置在位置 2, 但是還沒(méi)到重繪時(shí)刻, 所以它仍然顯示在位置 1 處同木。
- 為視圖指定一個(gè)從位置 1 到位置 2的動(dòng)畫。
- 等剩下的代碼執(zhí)行完畢
- 現(xiàn)在到重繪的時(shí)刻了跛十。如果沒(méi)有動(dòng)畫, 那么該視圖現(xiàn)在就會(huì)突然顯示在位置 2 上彤路。 但是如果有動(dòng)畫, 那么"動(dòng)畫電影" 就會(huì)出現(xiàn)。它從位置 1 開始展示視圖, 所以那仍舊是用戶所看到的芥映。
- 動(dòng)畫繼續(xù), 然后在位置 1 和位置 2 中間的位置顯示視圖洲尊。
- 動(dòng)畫停止, 在位置 2 處顯示結(jié)束視圖
- "動(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ù)組, 作為它的 animationImages 或 highlitedAnimationImages 屬性的值饲做。這個(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)畫繪制: 這些屬性是 alpha、 bounds旷档、center模叙、frame 、transform 還有(如果 那個(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!
另外一種從帶有動(dòng)畫的層級(jí)視圖中移除一個(gè)視圖的方法是調(diào)用 performSystemAnimation:onViews:options:animations:completion:, 第一個(gè)參數(shù)使用 。Delete(僅有的可用的第一個(gè)參數(shù))。這會(huì)讓那個(gè)視圖變模糊躲查、收縮和褪色, 之后再給它發(fā)送 removeFromSuperview 方法它浅。img
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)行的, 它倆混合在一塊兒。
在新的 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)畫腌乡。img
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)畫养篓。