CoreAnimation4-隱式動(dòng)畫(huà)和顯式動(dòng)畫(huà)

Core Animation基于一個(gè)假設(shè)泼返,說(shuō)屏幕上的任何東西都可以(或者可能)做動(dòng)畫(huà)。動(dòng)畫(huà)并不需要你在Core Animation中手動(dòng)打開(kāi),相反需要明確地關(guān)閉,否則他會(huì)一直存在衣吠。

當(dāng)你改變CALayer的一個(gè)可做動(dòng)畫(huà)的屬性,它并不能立刻在屏幕上體現(xiàn)出來(lái)壤靶。相反缚俏,它是從先前的值平滑過(guò)渡到新的值。這一切都是默認(rèn)的行為贮乳,你不需要做額外的操作忧换。

這看起來(lái)這太棒了,似乎不太真實(shí)向拆,我們來(lái)用一個(gè)demo解釋一下:首先和第一章“圖層樹(shù)”一樣創(chuàng)建一個(gè)藍(lán)色的方塊亚茬,然后添加一個(gè)按鈕,隨機(jī)改變它的顏色浓恳。代碼見(jiàn)清單7.1刹缝。點(diǎn)擊按鈕葡兑,你會(huì)發(fā)現(xiàn)圖層的顏色平滑過(guò)渡到一個(gè)新值,而不是跳變(圖7.1)赞草。

清單7.1 隨機(jī)改變圖層顏色

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView;

@property?(nonatomic,?weak)?IBOutlet?CALayer?*colorLayer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create sublayer

self.colorLayer?=?[CALayer?layer];

self.colorLayer.frame?=?CGRectMake(50.0f,?50.0f,?100.0f,?100.0f);

self.colorLayer.backgroundColor?=?[UIColor?blueColor].CGColor;

//add it to our view

[self.layerView.layer addSublayer:self.colorLayer];

}

-?(IBAction)changeColor

{

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.colorLayer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;??

}

@end

圖7.1 添加一個(gè)按鈕來(lái)控制圖層顏色

這其實(shí)就是所謂的隱式動(dòng)畫(huà)。之所以叫隱式是因?yàn)槲覀儾](méi)有指定任何動(dòng)畫(huà)的類(lèi)型吆鹤。我們僅僅改變了一個(gè)屬性厨疙,然后Core Animation來(lái)決定如何并且何時(shí)去做動(dòng)畫(huà)。Core Animaiton同樣支持顯式動(dòng)畫(huà)疑务,下章詳細(xì)說(shuō)明沾凄。

但當(dāng)你改變一個(gè)屬性,Core Animation是如何判斷動(dòng)畫(huà)類(lèi)型和持續(xù)時(shí)間的呢知允?實(shí)際上動(dòng)畫(huà)執(zhí)行的時(shí)間取決于當(dāng)前事務(wù)的設(shè)置撒蟀,動(dòng)畫(huà)類(lèi)型取決于圖層行為。

事務(wù)實(shí)際上是Core Animation用來(lái)包含一系列屬性動(dòng)畫(huà)集合的機(jī)制温鸽,任何用指定事務(wù)去改變可以做動(dòng)畫(huà)的圖層屬性都不會(huì)立刻發(fā)生變化保屯,而是當(dāng)事務(wù)一旦提交的時(shí)候開(kāi)始用一個(gè)動(dòng)畫(huà)過(guò)渡到新值。

事務(wù)是通過(guò)CATransaction類(lèi)來(lái)做管理涤垫,這個(gè)類(lèi)的設(shè)計(jì)有些奇怪姑尺,不像你從它的命名預(yù)期的那樣去管理一個(gè)簡(jiǎn)單的事務(wù),而是管理了一疊你不能訪問(wèn)的事務(wù)蝠猬。CATransaction沒(méi)有屬性或者實(shí)例方法,并且也不能用+alloc和-init方法創(chuàng)建它。但是可以用+begin和+commit分別來(lái)入椕骨簦或者出棧粥惧。

任何可以做動(dòng)畫(huà)的圖層屬性都會(huì)被添加到棧頂?shù)氖聞?wù),你可以通過(guò)+setAnimationDuration:方法設(shè)置當(dāng)前事務(wù)的動(dòng)畫(huà)時(shí)間匆绣,或者通過(guò)+animationDuration方法來(lái)獲取值(默認(rèn)0.25秒)驻右。

Core Animation在每個(gè)run loop周期中自動(dòng)開(kāi)始一次新的事務(wù)(run loop是iOS負(fù)責(zé)收集用戶輸入,處理定時(shí)器或者網(wǎng)絡(luò)事件并且重新繪制屏幕的東西)犬绒,即使你不顯式的用[CATransaction begin]開(kāi)始一次事務(wù)旺入,任何在一次run loop循環(huán)中屬性的改變都會(huì)被集中起來(lái),然后做一次0.25秒的動(dòng)畫(huà)凯力。

明白這些之后茵瘾,我們就可以輕松修改變色動(dòng)畫(huà)的時(shí)間了。我們當(dāng)然可以用當(dāng)前事務(wù)的+setAnimationDuration:方法來(lái)修改動(dòng)畫(huà)時(shí)間咐鹤,但在這里我們首先起一個(gè)新的事務(wù)拗秘,于是修改時(shí)間就不會(huì)有別的副作用。因?yàn)樾薷漠?dāng)前事務(wù)的時(shí)間可能會(huì)導(dǎo)致同一時(shí)刻別的動(dòng)畫(huà)(如屏幕旋轉(zhuǎn))祈惶,所以最好還是在調(diào)整動(dòng)畫(huà)之前壓入一個(gè)新的事務(wù)雕旨。

修改后的代碼見(jiàn)清單7.2扮匠。運(yùn)行程序,你會(huì)發(fā)現(xiàn)色塊顏色比之前變得更慢了凡涩。

清單7.2 使用CATransaction控制動(dòng)畫(huà)時(shí)間

-?(IBAction)changeColor

{

//begin a new transaction

[CATransaction?begin];

//set the animation duration to 1 second

[CATransaction?setAnimationDuration:1.0];

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.colorLayer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

?//commit the transaction

[CATransaction?commit];

}

如果你用過(guò)UIView的動(dòng)畫(huà)方法做過(guò)一些動(dòng)畫(huà)效果棒搜,那么應(yīng)該對(duì)這個(gè)模式不陌生。UIView有兩個(gè)方法活箕,+beginAnimations:context:和+commitAnimations力麸,和CATransaction的+begin和+commit方法類(lèi)似。實(shí)際上在+beginAnimations:context:和+commitAnimations之間所有視圖或者圖層屬性的改變而做的動(dòng)畫(huà)都是由于設(shè)置了CATransaction的原因育韩。

在iOS4中克蚂,蘋(píng)果對(duì)UIView添加了一種基于block的動(dòng)畫(huà)方法:+animateWithDuration:animations:。這樣寫(xiě)對(duì)做一堆的屬性動(dòng)畫(huà)在語(yǔ)法上會(huì)更加簡(jiǎn)單筋讨,但實(shí)質(zhì)上它們都是在做同樣的事情埃叭。

CATransaction的+begin和+commit方法在+animateWithDuration:animations:內(nèi)部自動(dòng)調(diào)用,這樣block中所有屬性的改變都會(huì)被事務(wù)所包含悉罕。這樣也可以避免開(kāi)發(fā)者由于對(duì)+begin和+commit匹配的失誤造成的風(fēng)險(xiǎn)赤屋。?

完成塊

基于UIView的block的動(dòng)畫(huà)允許你在動(dòng)畫(huà)結(jié)束的時(shí)候提供一個(gè)完成的動(dòng)作。CATranscation接口提供的+setCompletionBlock:方法也有同樣的功能蛮粮。我們來(lái)調(diào)整上個(gè)例子益缎,在顏色變化結(jié)束之后執(zhí)行一些操作。我們來(lái)添加一個(gè)完成之后的block然想,用來(lái)在每次顏色變化結(jié)束之后切換到另一個(gè)旋轉(zhuǎn)90的動(dòng)畫(huà)莺奔。代碼見(jiàn)清單7.3,運(yùn)行結(jié)果見(jiàn)圖7.2变泄。

清單7.3 在顏色動(dòng)畫(huà)完成之后添加一個(gè)回調(diào)

-?(IBAction)changeColor

{

//begin a new transaction

[CATransaction?begin];

//set the animation duration to 1 second

[CATransaction?setAnimationDuration:1.0];

//add the spin animation on completion

[CATransaction?setCompletionBlock:^{

//rotate the layer 90 degrees

CGAffineTransform?transform?=?self.colorLayer.affineTransform;

transform?=?CGAffineTransformRotate(transform,?M_PI_2);

self.colorLayer.affineTransform?=?transform;

}];

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.colorLayer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

//commit the transaction

[CATransaction?commit];

圖7.2 顏色漸變之完成之后再做一次旋轉(zhuǎn)

注意旋轉(zhuǎn)動(dòng)畫(huà)要比顏色漸變快得多令哟,這是因?yàn)橥瓿蓧K是在顏色漸變的事務(wù)提交并出棧之后才被執(zhí)行,于是妨蛹,用默認(rèn)的事務(wù)做變換屏富,默認(rèn)的時(shí)間也就變成了0.25秒。

圖層行為

現(xiàn)在來(lái)做個(gè)實(shí)驗(yàn)蛙卤,試著直接對(duì)UIView關(guān)聯(lián)的圖層做動(dòng)畫(huà)而不是一個(gè)單獨(dú)的圖層狠半。清單7.4是對(duì)清單7.2代碼的一點(diǎn)修改,移除了colorLayer颤难,并且直接設(shè)置layerView關(guān)聯(lián)圖層的背景色神年。

清單7.4 直接設(shè)置圖層的屬性

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//set the color of our layerView backing layer directly

self.layerView.layer.backgroundColor?=?[UIColor?blueColor].CGColor;

}

-?(IBAction)changeColor

{

//begin a new transaction

[CATransaction?begin];

//set the animation duration to 1 second

[CATransaction?setAnimationDuration:1.0];

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.layerView.layer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

//commit the transaction

[CATransaction?commit];

}

運(yùn)行程序,你會(huì)發(fā)現(xiàn)當(dāng)按下按鈕行嗤,圖層顏色瞬間切換到新的值已日,而不是之前平滑過(guò)渡的動(dòng)畫(huà)。發(fā)生了什么呢栅屏?隱式動(dòng)畫(huà)好像被UIView關(guān)聯(lián)圖層給禁用了飘千。

試想一下堂鲜,如果UIView的屬性都有動(dòng)畫(huà)特性的話,那么無(wú)論在什么時(shí)候修改它护奈,我們都應(yīng)該能注意到的缔莲。所以,如果說(shuō)UIKit建立在Core Animation(默認(rèn)對(duì)所有東西都做動(dòng)畫(huà))之上霉旗,那么隱式動(dòng)畫(huà)是如何被UIKit禁用掉呢酌予?

我們知道Core Animation通常對(duì)CALayer的所有屬性(可動(dòng)畫(huà)的屬性)做動(dòng)畫(huà),但是UIView把它關(guān)聯(lián)的圖層的這個(gè)特性關(guān)閉了奖慌。為了更好說(shuō)明這一點(diǎn),我們需要知道隱式動(dòng)畫(huà)是如何實(shí)現(xiàn)的松靡。

我們把改變屬性時(shí)CALayer自動(dòng)應(yīng)用的動(dòng)畫(huà)稱(chēng)作行為简僧,當(dāng)CALayer的屬性被修改時(shí)候,它會(huì)調(diào)用-actionForKey:方法雕欺,傳遞屬性的名稱(chēng)岛马。剩下的操作都在CALayer的頭文件中有詳細(xì)的說(shuō)明,實(shí)質(zhì)上是如下幾步:

圖層首先檢測(cè)它是否有委托屠列,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法啦逆。如果有,直接調(diào)用并返回結(jié)果笛洛。

如果沒(méi)有委托夏志,或者委托沒(méi)有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層接著檢查包含屬性名稱(chēng)對(duì)應(yīng)行為映射的actions字典苛让。

如果actions字典沒(méi)有包含對(duì)應(yīng)的屬性沟蔑,那么圖層接著在它的style字典接著搜索屬性名。

最后狱杰,如果在style里面也找不到對(duì)應(yīng)的行為瘦材,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法。

所以一輪完整的搜索結(jié)束之后仿畸,-actionForKey:要么返回空(這種情況下將不會(huì)有動(dòng)畫(huà)發(fā)生)食棕,要么是CAAction協(xié)議對(duì)應(yīng)的對(duì)象,最后CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫(huà)错沽。

于是這就解釋了UIKit是如何禁用隱式動(dòng)畫(huà)的:每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層都扮演了一個(gè)委托簿晓,并且提供了-actionForLayer:forKey的實(shí)現(xiàn)方法。當(dāng)不在一個(gè)動(dòng)畫(huà)塊的實(shí)現(xiàn)中甥捺,UIView對(duì)所有圖層行為返回nil抢蚀,但是在動(dòng)畫(huà)block范圍之內(nèi),它就返回了一個(gè)非空值镰禾。我們可以用一個(gè)demo做個(gè)簡(jiǎn)單的實(shí)驗(yàn)(清單7.5)

清單7.5 測(cè)試UIView的actionForLayer:forKey:實(shí)現(xiàn)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//test layer action when outside of animation block

NSLog(@"Outside: %@",?[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);

//begin animation block

[UIView?beginAnimations:nil?context:nil];

//test layer action when inside of animation block

NSLog(@"Inside: %@",?[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);

//end animation block

[UIView?commitAnimations];

}

@end

運(yùn)行程序皿曲,控制臺(tái)顯示結(jié)果如下:

$?LayerTest[:c07]?Outside:?$?LayerTest[:c07]?Inside:

于是我們可以預(yù)言唱逢,當(dāng)屬性在動(dòng)畫(huà)塊之外發(fā)生改變,UIView直接通過(guò)返回nil來(lái)禁用隱式動(dòng)畫(huà)屋休。但如果在動(dòng)畫(huà)塊范圍之內(nèi)坞古,根據(jù)動(dòng)畫(huà)具體類(lèi)型返回相應(yīng)的屬性,在這個(gè)例子就是CABasicAnimation(第八章“顯式動(dòng)畫(huà)”將會(huì)提到)劫樟。

當(dāng)然返回nil并不是禁用隱式動(dòng)畫(huà)唯一的辦法痪枫,CATransacition有個(gè)方法叫做+setDisableActions:,可以用來(lái)對(duì)所有屬性打開(kāi)或者關(guān)閉隱式動(dòng)畫(huà)叠艳。如果在清單7.2的[CATransaction begin]之后添加下面的代碼奶陈,同樣也會(huì)阻止動(dòng)畫(huà)的發(fā)生:

[CATransaction?setDisableActions:YES];

總結(jié)一下,我們知道了如下幾點(diǎn)

UIView關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫(huà)附较,對(duì)這種圖層做動(dòng)畫(huà)的唯一辦法就是使用UIView的動(dòng)畫(huà)函數(shù)(而不是依賴(lài)CATransaction)吃粒,或者繼承UIView,并覆蓋-actionForLayer:forKey:方法拒课,或者直接創(chuàng)建一個(gè)顯式動(dòng)畫(huà)(具體細(xì)節(jié)見(jiàn)第八章)徐勃。

對(duì)于單獨(dú)存在的圖層,我們可以通過(guò)實(shí)現(xiàn)圖層的-actionForLayer:forKey:委托方法早像,或者提供一個(gè)actions字典來(lái)控制隱式動(dòng)畫(huà)僻肖。

我們來(lái)對(duì)顏色漸變的例子使用一個(gè)不同的行為,通過(guò)給colorLayer設(shè)置一個(gè)自定義的actions字典卢鹦。我們也可以使用委托來(lái)實(shí)現(xiàn)臀脏,但是actions字典可以寫(xiě)更少的代碼。那么到底改如何創(chuàng)建一個(gè)合適的行為對(duì)象呢冀自?

行為通常是一個(gè)被Core Animation隱式調(diào)用的顯式動(dòng)畫(huà)對(duì)象谁榜。這里我們使用的是一個(gè)實(shí)現(xiàn)了CATransaction的實(shí)例,叫做推進(jìn)過(guò)渡凡纳。

第八章中將會(huì)詳細(xì)解釋過(guò)渡窃植,不過(guò)對(duì)于現(xiàn)在,知道CATransition響應(yīng)CAAction協(xié)議荐糜,并且可以當(dāng)做一個(gè)圖層行為就足夠了巷怜。結(jié)果很贊,不論在什么時(shí)候改變背景顏色暴氏,新的色塊都是從左側(cè)滑入延塑,而不是默認(rèn)的漸變效果。

清單7.6 實(shí)現(xiàn)自定義行為

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView;

@property?(nonatomic,?weak)?IBOutlet?CALayer?*colorLayer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create sublayer

self.colorLayer?=?[CALayer?layer];

self.colorLayer.frame?=?CGRectMake(50.0f,?50.0f,?100.0f,?100.0f);

self.colorLayer.backgroundColor?=?[UIColor?blueColor].CGColor;

//add a custom action

CATransition?*transition?=?[CATransition?animation];

transition.type?=?kCATransitionPush;

transition.subtype?=?kCATransitionFromLeft;

self.colorLayer.actions?=?@{@"backgroundColor":?transition};

//add it to our view

[self.layerView.layer addSublayer:self.colorLayer];

}

-?(IBAction)changeColor

{

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.colorLayer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

}

@end

圖7.3 使用推進(jìn)過(guò)渡的色值動(dòng)畫(huà)

呈現(xiàn)與模型

CALayer的屬性行為其實(shí)很不正常答渔,因?yàn)楦淖円粋€(gè)圖層的屬性并沒(méi)有立刻生效关带,而是通過(guò)一段時(shí)間漸變更新。這是怎么做到的呢?

當(dāng)你改變一個(gè)圖層的屬性宋雏,屬性值的確是立刻更新的(如果你讀取它的數(shù)據(jù)芜飘,你會(huì)發(fā)現(xiàn)它的值在你設(shè)置它的那一刻就已經(jīng)生效了),但是屏幕上并沒(méi)有馬上發(fā)生改變磨总。這是因?yàn)槟阍O(shè)置的屬性并沒(méi)有直接調(diào)整圖層的外觀嗦明,相反,他只是定義了圖層動(dòng)畫(huà)結(jié)束之后將要變化的外觀蚪燕。

當(dāng)設(shè)置CALayer的屬性娶牌,實(shí)際上是在定義當(dāng)前事務(wù)結(jié)束之后圖層如何顯示的模型。Core Animation扮演了一個(gè)控制器的角色馆纳,并且負(fù)責(zé)根據(jù)圖層行為和事務(wù)設(shè)置去不斷更新視圖的這些屬性在屏幕上的狀態(tài)诗良。

我們討論的就是一個(gè)典型的微型MVC模式。CALayer是一個(gè)連接用戶界面(就是MVC中的view)虛構(gòu)的類(lèi)鲁驶,但是在界面本身這個(gè)場(chǎng)景下累榜,CALayer的行為更像是存儲(chǔ)了視圖如何顯示和動(dòng)畫(huà)的數(shù)據(jù)模型。實(shí)際上灵嫌,在蘋(píng)果自己的文檔中,圖層樹(shù)通常都是值的圖層樹(shù)模型葛作。

在iOS中寿羞,屏幕每秒鐘重繪60次。如果動(dòng)畫(huà)時(shí)長(zhǎng)比60分之一秒要長(zhǎng)赂蠢,Core Animation就需要在設(shè)置一次新值和新值生效之間绪穆,對(duì)屏幕上的圖層進(jìn)行重新組織。這意味著CALayer除了“真實(shí)”值(就是你設(shè)置的值)之外虱岂,必須要知道當(dāng)前顯示在屏幕上的屬性值的記錄玖院。

每個(gè)圖層屬性的顯示值都被存儲(chǔ)在一個(gè)叫做呈現(xiàn)圖層的獨(dú)立圖層當(dāng)中,他可以通過(guò)-presentationLayer方法來(lái)訪問(wèn)第岖。這個(gè)呈現(xiàn)圖層實(shí)際上是模型圖層的復(fù)制难菌,但是它的屬性值代表了在任何指定時(shí)刻當(dāng)前外觀效果。換句話說(shuō)蔑滓,你可以通過(guò)呈現(xiàn)圖層的值來(lái)獲取當(dāng)前屏幕上真正顯示出來(lái)的值(圖7.4)郊酒。

我們?cè)诘谝徽轮刑岬匠藞D層樹(shù),另外還有呈現(xiàn)樹(shù)键袱。呈現(xiàn)樹(shù)通過(guò)圖層樹(shù)中所有圖層的呈現(xiàn)圖層所形成燎窘。注意呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時(shí)候創(chuàng)建,所以在那之前調(diào)用-presentationLayer將會(huì)返回nil蹄咖。

你可能注意到有一個(gè)叫做–modelLayer的方法褐健。在呈現(xiàn)圖層上調(diào)用–modelLayer將會(huì)返回它正在呈現(xiàn)所依賴(lài)的CALayer。通常在一個(gè)圖層上調(diào)用-modelLayer會(huì)返回–self(實(shí)際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)澜汤。

圖7.4 一個(gè)移動(dòng)的圖層是如何通過(guò)數(shù)據(jù)模型呈現(xiàn)的

大多數(shù)情況下蚜迅,你不需要直接訪問(wèn)呈現(xiàn)圖層舵匾,你可以通過(guò)和模型圖層的交互,來(lái)讓Core Animation更新顯示慢叨。兩種情況下呈現(xiàn)圖層會(huì)變得很有用纽匙,一個(gè)是同步動(dòng)畫(huà),一個(gè)是處理用戶交互拍谐。

如果你在實(shí)現(xiàn)一個(gè)基于定時(shí)器的動(dòng)畫(huà)(見(jiàn)第11章“基于定時(shí)器的動(dòng)畫(huà)”)烛缔,而不僅僅是基于事務(wù)的動(dòng)畫(huà),這個(gè)時(shí)候準(zhǔn)確地知道在某一時(shí)刻圖層顯示在什么位置就會(huì)對(duì)正確擺放圖層很有用了轩拨。

如果你想讓你做動(dòng)畫(huà)的圖層響應(yīng)用戶輸入践瓷,你可以使用-hitTest:方法(見(jiàn)第三章“圖層幾何學(xué)”)來(lái)判斷指定圖層是否被觸摸,這時(shí)候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會(huì)顯得更有意義亡蓉,因?yàn)槌尸F(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置晕翠,而不是當(dāng)前動(dòng)畫(huà)結(jié)束之后的位置。

我們可以用一個(gè)簡(jiǎn)單的案例來(lái)證明后者(見(jiàn)清單7.7)砍濒。在這個(gè)例子中淋肾,點(diǎn)擊屏幕上的任意位置將會(huì)讓圖層平移到那里。點(diǎn)擊圖層本身可以隨機(jī)改變它的顏色爸邢。我們通過(guò)對(duì)呈現(xiàn)圖層調(diào)用-hitTest:來(lái)判斷是否被點(diǎn)擊樊卓。

如果修改代碼讓-hitTest:直接作用于colorLayer而不是呈現(xiàn)圖層,你會(huì)發(fā)現(xiàn)當(dāng)圖層移動(dòng)的時(shí)候它并不能正確顯示杠河。這時(shí)候你就需要點(diǎn)擊圖層將要移動(dòng)到的位置而不是圖層本身來(lái)響應(yīng)點(diǎn)擊(這就是為什么用呈現(xiàn)圖層來(lái)響應(yīng)交互的原因)碌尔。

清單7.7 使用presentationLayer圖層來(lái)判斷當(dāng)前圖層位置

@interface?ViewController?()

@property?(nonatomic,?strong)?CALayer?*colorLayer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create a red layer

self.colorLayer?=?[CALayer?layer];

self.colorLayer.frame?=?CGRectMake(,?,?,?);

self.colorLayer.position?=?CGPointMake(self.view.bounds.size.width?/?,?self.view.bounds.size.height?/?);

self.colorLayer.backgroundColor?=?[UIColor?redColor].CGColor;

[self.view.layer addSublayer:self.colorLayer];

}

-?(void)touchesBegan:(NSSet?*)touches withEvent:(UIEvent?*)event

{

//get the touch point

CGPoint?point?=?[[touches anyObject]?locationInView:self.view];

//check if we've tapped the moving layer

if?([self.colorLayer.presentationLayer hitTest:point])?{

//randomize the layer background color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.colorLayer.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

}?else?{

//otherwise (slowly) move the layer to new position

[CATransaction?begin];

[CATransaction?setAnimationDuration:4.0];

self.colorLayer.position?=?point;

[CATransaction?commit];

}

}

總結(jié)

這一章討論了隱式動(dòng)畫(huà),還有Core Animation對(duì)指定屬性選擇合適的動(dòng)畫(huà)行為的機(jī)制券敌。同時(shí)你知道了UIKit是如何充分利用Core Animation的隱式動(dòng)畫(huà)機(jī)制來(lái)強(qiáng)化它的顯式系統(tǒng)唾戚,以及動(dòng)畫(huà)是如何被默認(rèn)禁用并且當(dāng)需要的時(shí)候啟用的。最后待诅,你了解了呈現(xiàn)和模型圖層叹坦,以及Core Animation是如何通過(guò)它們來(lái)判斷出圖層當(dāng)前位置以及將要到達(dá)的位置。

顯式動(dòng)畫(huà)

上一章介紹了隱式動(dòng)畫(huà)的概念卑雁。隱式動(dòng)畫(huà)是在iOS平臺(tái)創(chuàng)建動(dòng)態(tài)用戶界面的一種直接方式立由,也是UIKit動(dòng)畫(huà)機(jī)制的基礎(chǔ),不過(guò)它并不能涵蓋所有的動(dòng)畫(huà)類(lèi)型序厉。在這一章中锐膜,我們將要研究一下顯式動(dòng)畫(huà),它能夠?qū)σ恍傩宰鲋付ǖ淖远x動(dòng)畫(huà)弛房,或者創(chuàng)建非線性動(dòng)畫(huà)道盏,比如沿著任意一條曲線移動(dòng)。

屬性動(dòng)畫(huà)

首先我們來(lái)探討一下屬性動(dòng)畫(huà)。屬性動(dòng)畫(huà)作用于圖層的某個(gè)單一屬性荷逞,并指定了它的一個(gè)目標(biāo)值媒咳,或者一連串將要做動(dòng)畫(huà)的值。屬性動(dòng)畫(huà)分為兩種:基礎(chǔ)和關(guān)鍵幀种远。

基礎(chǔ)動(dòng)畫(huà)

動(dòng)畫(huà)其實(shí)就是一段時(shí)間內(nèi)發(fā)生的改變涩澡,最簡(jiǎn)單的形式就是從一個(gè)值改變到另一個(gè)值,這也是CABasicAnimation最主要的功能坠敷。CABasicAnimation是CAPropertyAnimation的一個(gè)子類(lèi)妙同,CAPropertyAnimation同時(shí)也是Core Animation所有動(dòng)畫(huà)類(lèi)型的抽象基類(lèi)。作為一個(gè)抽象類(lèi)膝迎,CAAnimation本身并沒(méi)有做多少工作粥帚,它提供了一個(gè)計(jì)時(shí)函數(shù)(見(jiàn)第十章“緩沖”),一個(gè)委托(用于反饋動(dòng)畫(huà)狀態(tài))以及一個(gè)removedOnCompletion限次,用于標(biāo)識(shí)動(dòng)畫(huà)是否該在結(jié)束后自動(dòng)釋放(默認(rèn)YES芒涡,為了防止內(nèi)存泄露)。CAAnimation同時(shí)實(shí)現(xiàn)了一些協(xié)議卖漫,包括CAAction(允許CAAnimation的子類(lèi)可以提供圖層行為)费尽,以及CAMediaTiming(第九章“圖層時(shí)間”將會(huì)詳細(xì)解釋?zhuān)?/p>

CAPropertyAnimation通過(guò)指定動(dòng)畫(huà)的keyPath作用于一個(gè)單一屬性,CAAnimation通常應(yīng)用于一個(gè)指定的CALayer羊始,于是這里指的也就是一個(gè)圖層的keyPath了旱幼。實(shí)際上它是一個(gè)關(guān)鍵路徑(一些用點(diǎn)表示法可以在層級(jí)關(guān)系中指向任意嵌套的對(duì)象),而不僅僅是一個(gè)屬性的名稱(chēng)店枣,因?yàn)檫@意味著動(dòng)畫(huà)不僅可以作用于圖層本身的屬性,而且還包含了它的子成員的屬性叹誉,甚至是一些虛擬的屬性(后面會(huì)詳細(xì)解釋?zhuān)?/p>

CABasicAnimation繼承于CAPropertyAnimation鸯两,并添加了如下屬性:

id fromValue

id toValue

id byValue

從命名就可以得到很好的解釋?zhuān)篺romValue代表了動(dòng)畫(huà)開(kāi)始之前屬性的值,toValue代表了動(dòng)畫(huà)結(jié)束之后的值长豁,byValue代表了動(dòng)畫(huà)執(zhí)行過(guò)程中改變的值钧唐。

通過(guò)組合這三個(gè)屬性就可以有很多種方式來(lái)指定一個(gè)動(dòng)畫(huà)的過(guò)程。它們被定義成id類(lèi)型而不是一些具體的類(lèi)型是因?yàn)閷傩詣?dòng)畫(huà)可以用作很多不同種的屬性類(lèi)型匠襟,包括數(shù)字類(lèi)型钝侠,矢量,變換矩陣酸舍,甚至是顏色或者圖片帅韧。

id類(lèi)型可以包含任意由NSObject派生的對(duì)象,但有時(shí)候你會(huì)希望對(duì)一些不直接從NSObject繼承的屬性類(lèi)型做動(dòng)畫(huà)啃勉,這意味著你需要把這些值用一個(gè)對(duì)象來(lái)封裝忽舟,或者強(qiáng)轉(zhuǎn)成一個(gè)對(duì)象,就像某些功能和Objective-C對(duì)象類(lèi)似的Core Foundation類(lèi)型。但是如何從一個(gè)具體的數(shù)據(jù)類(lèi)型轉(zhuǎn)換成id看起來(lái)并不明顯叮阅,一些普通的例子見(jiàn)表8.1刁品。

表8.1 用于CAPropertyAnimation的一些類(lèi)型轉(zhuǎn)換

fromValue,toValue和byValue屬性可以用很多種方式來(lái)組合浩姥,但為了防止沖突挑随,不能一次性同時(shí)指定這三個(gè)值。例如勒叠,如果指定了fromValue等于2兜挨,toValue等于4,byValue等于3缴饭,那么Core Animation就不知道結(jié)果到底是4(toValue)還是5(fromValue + byValue)了暑劝。他們的用法在CABasicAnimation頭文件中已經(jīng)描述的很清楚了,所以在這里就不重復(fù)了颗搂〉C停總的說(shuō)來(lái),就是只需要指定toValue或者byValue丢氢,剩下的值都可以通過(guò)上下文自動(dòng)計(jì)算出來(lái)傅联。

舉個(gè)例子:我們修改一下第七章中的顏色漸變的動(dòng)畫(huà),用顯式的CABasicAnimation來(lái)取代之前的隱式動(dòng)畫(huà)疚察,代碼見(jiàn)清單8.1蒸走。

清單8.1 通過(guò)CABasicAnimation來(lái)設(shè)置圖層背景色

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView;

@property?(nonatomic,?strong)?IBOutlet?CALayer?*colorLayer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create sublayer

self.colorLayer?=?[CALayer?layer];

self.colorLayer.frame?=?CGRectMake(50.0f,?50.0f,?100.0f,?100.0f);

self.colorLayer.backgroundColor?=?[UIColor?blueColor].CGColor;

//add it to our view

[self.layerView.layer addSublayer:self.colorLayer];

}

-?(IBAction)changeColor

{

?//create a new random color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

UIColor?*color?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0];

//create a basic animation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"backgroundColor";

animation.toValue?=?(__bridge id)color.CGColor;

//apply animation to layer

[self.colorLayer addAnimation:animation forKey:nil];

}

@end

運(yùn)行程序,結(jié)果有點(diǎn)差強(qiáng)人意貌嫡,點(diǎn)擊按鈕比驻,的確可以使圖層動(dòng)畫(huà)過(guò)渡到一個(gè)新的顏色,然動(dòng)畫(huà)結(jié)束之后又立刻變回原始值岛抄。

這是因?yàn)閯?dòng)畫(huà)并沒(méi)有改變圖層的模型别惦,而只是呈現(xiàn)(第七章)。一旦動(dòng)畫(huà)結(jié)束并從圖層上移除之后夫椭,圖層就立刻恢復(fù)到之前定義的外觀狀態(tài)掸掸。我們從沒(méi)改變過(guò)backgroundColor屬性,所以圖層就返回到原始的顏色蹭秋。

當(dāng)之前在使用隱式動(dòng)畫(huà)的時(shí)候扰付,實(shí)際上它就是用例子中CABasicAnimation來(lái)實(shí)現(xiàn)的(回憶第七章,我們?cè)?actionForLayer:forKey:委托方法打印出來(lái)的結(jié)果就是CABasicAnimation)仁讨。但是在那個(gè)例子中羽莺,我們通過(guò)設(shè)置屬性來(lái)打開(kāi)動(dòng)畫(huà)。在這里我們做了相同的動(dòng)畫(huà)洞豁,但是并沒(méi)有設(shè)置任何屬性的值(這就是為什么會(huì)立刻變回初始狀態(tài)的原因)禽翼。

把動(dòng)畫(huà)設(shè)置成一個(gè)圖層的行為(然后通過(guò)改變屬性值來(lái)啟動(dòng)動(dòng)畫(huà))是到目前為止同步屬性值和動(dòng)畫(huà)狀態(tài)最簡(jiǎn)單的方式了屠橄,假設(shè)由于某些原因我們不能這么做(通常因?yàn)閁IView關(guān)聯(lián)的圖層不能這么做動(dòng)畫(huà)),那么有兩種可以更新屬性值的方式:在動(dòng)畫(huà)開(kāi)始之前或者動(dòng)畫(huà)結(jié)束之后闰挡。

動(dòng)畫(huà)之前改變屬性的值是最簡(jiǎn)單的辦法锐墙,但這意味著我們不能使用fromValue這么好的特性了,而且要手動(dòng)將fromValue設(shè)置成圖層當(dāng)前的值长酗。

于是在動(dòng)畫(huà)創(chuàng)建之前插入如下代碼溪北,就可以解決問(wèn)題了

animation.fromValue?=?(__bridge id)self.colorLayer.backgroundColor;

self.colorLayer.backgroundColor?=?color.CGColor;

這的確是可行的,但還是有些問(wèn)題夺脾,如果這里已經(jīng)正在進(jìn)行一段動(dòng)畫(huà)之拨,我們需要從呈現(xiàn)圖層那里去獲得fromValue,而不是模型圖層咧叭。另外蚀乔,由于這里的圖層并不是UIView關(guān)聯(lián)的圖層,我們需要用CATransaction來(lái)禁用隱式動(dòng)畫(huà)行為菲茬,否則默認(rèn)的圖層行為會(huì)干擾我們的顯式動(dòng)畫(huà)(實(shí)際上吉挣,顯式動(dòng)畫(huà)通常會(huì)覆蓋隱式動(dòng)畫(huà),但在文章中并沒(méi)有提到婉弹,所以為了安全最好這么做)睬魂。

更新之后的代碼如下:

CALayer?*layer?=?self.colorLayer.presentationLayer??:

self.colorLayer;

animation.fromValue?=?(__bridge id)layer.backgroundColor;

[CATransaction?begin];

[CATransaction?setDisableActions:YES];

self.colorLayer.backgroundColor?=?color.CGColor;

[CATransaction?commit];

如果給每個(gè)動(dòng)畫(huà)都添加這些,代碼會(huì)顯得特別臃腫镀赌。幸運(yùn)的是氯哮,我們可以從CABasicAnimation去自動(dòng)設(shè)置這些。于是可以創(chuàng)建一個(gè)可復(fù)用的代碼商佛。清單8.2修改了之前的示例喉钢,通過(guò)使用CABasicAnimation的一個(gè)函數(shù)來(lái)避免在每次動(dòng)畫(huà)時(shí)候都重復(fù)那些臃腫的代碼。

清單8.2 修改動(dòng)畫(huà)立刻恢復(fù)到原始狀態(tài)的一個(gè)可復(fù)用函數(shù)

-?(void)applyBasicAnimation:(CABasicAnimation?*)animation toLayer:(CALayer?*)layer

{

//set the from value (using presentation layer if available)

animation.fromValue?=?[layer.presentationLayer??:?layer valueForKeyPath:animation.keyPath];

//update the property in advance

//note: this approach will only work if toValue != nil

[CATransaction?begin];

[CATransaction?setDisableActions:YES];

[layer setValue:animation.toValue forKeyPath:animation.keyPath];

[CATransaction?commit];

//apply animation to layer

[layer addAnimation:animation forKey:nil];

}

-?(IBAction)changeColor

{

//create a new random color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

UIColor?*color?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0];

//create a basic animation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"backgroundColor";

animation.toValue?=?(__bridge id)color.CGColor;

//apply animation without snap-back

[self?applyBasicAnimation:animation toLayer:self.colorLayer];

}

這種簡(jiǎn)單的實(shí)現(xiàn)方式通過(guò)toValue而不是byValue來(lái)處理動(dòng)畫(huà)良姆,不過(guò)這已經(jīng)是朝更好的解決方案邁出一大步了肠虽。你可以把它添加給CALaye作為一個(gè)分類(lèi),以方便更好地使用歇盼。

解決看起來(lái)如此簡(jiǎn)單的一個(gè)問(wèn)題都著實(shí)麻煩舔痕,但是別的方案會(huì)更加復(fù)雜评抚。如果不在動(dòng)畫(huà)開(kāi)始之前去更新目標(biāo)屬性豹缀,那么就只能在動(dòng)畫(huà)完全結(jié)束或者取消的時(shí)候更新它。這意味著我們需要精準(zhǔn)地在動(dòng)畫(huà)結(jié)束之后慨代,圖層返回到原始值之前更新屬性邢笙。那么該如何找到這個(gè)點(diǎn)呢?

CAAnimationDelegate

在第七章使用隱式動(dòng)畫(huà)的時(shí)候侍匙,我們可以在CATransaction完成塊中檢測(cè)到動(dòng)畫(huà)的完成氮惯。但是這種方式并不適用于顯式動(dòng)畫(huà)叮雳,因?yàn)檫@里的動(dòng)畫(huà)和事務(wù)并沒(méi)太多關(guān)聯(lián)。

那么為了知道一個(gè)顯式動(dòng)畫(huà)在何時(shí)結(jié)束妇汗,我們需要使用一個(gè)實(shí)現(xiàn)了CAAnimationDelegate協(xié)議的delegate帘不。

CAAnimationDelegate在任何頭文件中都找不到亭珍,但是可以在CAAnimation頭文件或者蘋(píng)果開(kāi)發(fā)者文檔中找到相關(guān)函數(shù)苹支。在這個(gè)例子中,我們用-animationDidStop:finished:方法在動(dòng)畫(huà)結(jié)束之后來(lái)更新圖層的backgroundColor级历。

當(dāng)更新屬性的時(shí)候互婿,我們需要設(shè)置一個(gè)新的事務(wù)捣郊,并且禁用圖層行為。否則動(dòng)畫(huà)會(huì)發(fā)生兩次慈参,一個(gè)是因?yàn)轱@式的CABasicAnimation呛牲,另一次是因?yàn)殡[式動(dòng)畫(huà),具體實(shí)現(xiàn)見(jiàn)訂單8.3驮配。

清單8.3 動(dòng)畫(huà)完成之后修改圖層的背景色

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create sublayer

self.colorLayer?=?[CALayer?layer];

self.colorLayer.frame?=?CGRectMake(50.0f,?50.0f,?100.0f,?100.0f);

self.colorLayer.backgroundColor?=?[UIColor?blueColor].CGColor;

//add it to our view

[self.layerView.layer addSublayer:self.colorLayer];

}

-?(IBAction)changeColor

{

//create a new random color

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

UIColor?*color?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0];

//create a basic animation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"backgroundColor";

animation.toValue?=?(__bridge id)color.CGColor;

animation.delegate?=?self;

//apply animation to layer

[self.colorLayer addAnimation:animation forKey:nil];

}

-?(void)animationDidStop:(CABasicAnimation?*)anim finished:(BOOL)flag

{

//set the backgroundColor property to match animation toValue

[CATransaction?begin];

[CATransaction?setDisableActions:YES];

self.colorLayer.backgroundColor?=?(__bridge?CGColorRef)anim.toValue;

[CATransaction?commit];

}

@end

對(duì)CAAnimation而言娘扩,使用委托模式而不是一個(gè)完成塊會(huì)帶來(lái)一個(gè)問(wèn)題,就是當(dāng)你有多個(gè)動(dòng)畫(huà)的時(shí)候僧凤,無(wú)法在在回調(diào)方法中區(qū)分畜侦。在一個(gè)視圖控制器中創(chuàng)建動(dòng)畫(huà)的時(shí)候,通常會(huì)用控制器本身作為一個(gè)委托(如清單8.3所示)躯保,但是所有的動(dòng)畫(huà)都會(huì)調(diào)用同一個(gè)回調(diào)方法旋膳,所以你就需要判斷到底是那個(gè)圖層的調(diào)用。

考慮一下第三章的鬧鐘途事,“圖層幾何學(xué)”验懊,我們通過(guò)簡(jiǎn)單地每秒更新指針的角度來(lái)實(shí)現(xiàn)一個(gè)鐘,但如果指針動(dòng)態(tài)地轉(zhuǎn)向新的位置會(huì)更加真實(shí)尸变。

我們不能通過(guò)隱式動(dòng)畫(huà)來(lái)實(shí)現(xiàn)因?yàn)檫@些指針都是UIView的實(shí)例义图,所以圖層的隱式動(dòng)畫(huà)都被禁用了。我們可以簡(jiǎn)單地通過(guò)UIView的動(dòng)畫(huà)方法來(lái)實(shí)現(xiàn)召烂。但如果想更好地控制動(dòng)畫(huà)時(shí)間碱工,使用顯式動(dòng)畫(huà)會(huì)更好(更多內(nèi)容見(jiàn)第十章)。使用CABasicAnimation來(lái)做動(dòng)畫(huà)可能會(huì)更加復(fù)雜奏夫,因?yàn)槲覀冃枰?animationDidStop:finished:中檢測(cè)指針狀態(tài)(用于設(shè)置結(jié)束的位置)怕篷。

動(dòng)畫(huà)本身會(huì)作為一個(gè)參數(shù)傳入委托的方法,也許你會(huì)認(rèn)為可以控制器中把動(dòng)畫(huà)存儲(chǔ)為一個(gè)屬性酗昼,然后在回調(diào)用比較廊谓,但實(shí)際上并不起作用,因?yàn)槲袀魅氲膭?dòng)畫(huà)參數(shù)是原始值的一個(gè)深拷貝麻削,從而不是同一個(gè)值蒸痹。

當(dāng)使用-addAnimation:forKey:把動(dòng)畫(huà)添加到圖層春弥,這里有一個(gè)到目前為止我們都設(shè)置為nil的key參數(shù)。這里的鍵是-animationForKey:方法找到對(duì)應(yīng)動(dòng)畫(huà)的唯一標(biāo)識(shí)符叠荠,而當(dāng)前動(dòng)畫(huà)的所有鍵都可以用animationKeys獲取匿沛。如果我們對(duì)每個(gè)動(dòng)畫(huà)都關(guān)聯(lián)一個(gè)唯一的鍵,就可以對(duì)每個(gè)圖層循環(huán)所有鍵榛鼎,然后調(diào)用-animationForKey:來(lái)比對(duì)結(jié)果俺祠。盡管這不是一個(gè)優(yōu)雅的實(shí)現(xiàn)。

幸運(yùn)的是借帘,還有一種更加簡(jiǎn)單的方法蜘渣。像所有的NSObject子類(lèi)一樣,CAAnimation實(shí)現(xiàn)了KVC(鍵-值-編碼)協(xié)議肺然,于是你可以用-setValue:forKey:和-valueForKey:方法來(lái)存取屬性蔫缸。但是CAAnimation有一個(gè)不同的性能:它更像一個(gè)NSDictionary,可以讓你隨意設(shè)置鍵值對(duì)际起,即使和你使用的動(dòng)畫(huà)類(lèi)所聲明的屬性并不匹配拾碌。

這意味著你可以對(duì)動(dòng)畫(huà)用任意類(lèi)型打標(biāo)簽。在這里街望,我們給UIView類(lèi)型的指針添加的動(dòng)畫(huà)校翔,所以可以簡(jiǎn)單地判斷動(dòng)畫(huà)到底屬于哪個(gè)視圖,然后在委托方法中用這個(gè)信息正確地更新鐘的指針(清單8.4)灾前。

清單8.4 使用KVC對(duì)動(dòng)畫(huà)打標(biāo)簽

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*hourHand;

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*minuteHand;

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*secondHand;

@property?(nonatomic,?weak)?NSTimer?*timer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//adjust anchor points

self.secondHand.layer.anchorPoint?=?CGPointMake(0.5f,?0.9f);

self.minuteHand.layer.anchorPoint?=?CGPointMake(0.5f,?0.9f);

self.hourHand.layer.anchorPoint?=?CGPointMake(0.5f,?0.9f);

//start timer

self.timer?=?[NSTimer?scheduledTimerWithTimeInterval:1.0?target:self?selector:@selector(tick)?userInfo:nil?repeats:YES];

//set initial hand positions

[self?updateHandsAnimated:NO];

}

-?(void)tick

{

[self?updateHandsAnimated:YES];

}

-?(void)updateHandsAnimated:(BOOL)animated

{

//convert time to hours, minutes and seconds

NSCalendar?*calendar?=?[[NSCalendar?alloc]?initWithCalendarIdentifier:NSGregorianCalendar];

NSUInteger?units?=?NSHourCalendarUnit?|?NSMinuteCalendarUnit?|?NSSecondCalendarUnit;

NSDateComponents?*components?=?[calendar components:units fromDate:[NSDate?date]];

CGFloat?hourAngle?=?(components.hour?/?12.0)?*?M_PI?*?2.0;

//calculate hour hand angle //calculate minute hand angle

CGFloat?minuteAngle?=?(components.minute?/?60.0)?*?M_PI?*?2.0;

//calculate second hand angle

CGFloat?secondAngle?=?(components.second?/?60.0)?*?M_PI?*?2.0;

//rotate hands

[self?setAngle:hourAngle forHand:self.hourHand animated:animated];

[self?setAngle:minuteAngle forHand:self.minuteHand animated:animated];

[self?setAngle:secondAngle forHand:self.secondHand animated:animated];

}

-?(void)setAngle:(CGFloat)angle forHand:(UIView?*)handView animated:(BOOL)animated

{

//generate transform

CATransform3D?transform?=?CATransform3DMakeRotation(angle,?,?,?);

if?(animated)?{

//create transform animation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

[self?updateHandsAnimated:NO];

animation.keyPath?=?@"transform";

animation.toValue?=?[NSValue?valueWithCATransform3D:transform];

animation.duration?=?0.5;

animation.delegate?=?self;

[animation setValue:handView forKey:@"handView"];

[handView.layer addAnimation:animation forKey:nil];

}?else?{

//set transform directly

handView.layer.transform?=?transform;

}

}

-?(void)animationDidStop:(CABasicAnimation?*)anim finished:(BOOL)flag

{

//set final position for hand view

UIView?*handView?=?[anim valueForKey:@"handView"];

handView.layer.transform?=?[anim.toValue?CATransform3DValue];

我們成功的識(shí)別出每個(gè)圖層停止動(dòng)畫(huà)的時(shí)間防症,然后更新它的變換到一個(gè)新值,很好哎甲。

不幸的是蔫敲,即使做了這些,還是有個(gè)問(wèn)題炭玫,清單8.4在模擬器上運(yùn)行的很好奈嘿,但當(dāng)真正跑在iOS設(shè)備上時(shí),我們發(fā)現(xiàn)在-animationDidStop:finished:委托方法調(diào)用之前吞加,指針會(huì)迅速返回到原始值裙犹,這個(gè)清單8.3圖層顏色發(fā)生的情況一樣。

問(wèn)題在于回調(diào)方法在動(dòng)畫(huà)完成之前已經(jīng)被調(diào)用了衔憨,但不能保證這發(fā)生在屬性動(dòng)畫(huà)返回初始狀態(tài)之前叶圃。這同時(shí)也很好地說(shuō)明了為什么要在真實(shí)的設(shè)備上測(cè)試動(dòng)畫(huà)代碼,而不僅僅是模擬器巫财。

我們可以用一個(gè)fillMode屬性來(lái)解決這個(gè)問(wèn)題盗似,下一章會(huì)詳細(xì)說(shuō)明哩陕,這里知道在動(dòng)畫(huà)之前設(shè)置它比在動(dòng)畫(huà)結(jié)束之后更新屬性更加方便平项。

關(guān)鍵幀動(dòng)畫(huà)

CABasicAnimation揭示了大多數(shù)隱式動(dòng)畫(huà)背后依賴(lài)的機(jī)制赫舒,這的確很有趣,但是顯式地給圖層添加CABasicAnimation相較于隱式動(dòng)畫(huà)而言闽瓢,只能說(shuō)費(fèi)力不討好接癌。

CAKeyframeAnimation是另一種UIKit沒(méi)有暴露出來(lái)但功能強(qiáng)大的類(lèi)。和CABasicAnimation類(lèi)似扣讼,CAKeyframeAnimation同樣是CAPropertyAnimation的一個(gè)子類(lèi)缺猛,它依然作用于單一的一個(gè)屬性,但是和CABasicAnimation不一樣的是椭符,它不限制于設(shè)置一個(gè)起始和結(jié)束的值荔燎,而是可以根據(jù)一連串隨意的值來(lái)做動(dòng)畫(huà)。

關(guān)鍵幀起源于傳動(dòng)動(dòng)畫(huà)销钝,意思是指主導(dǎo)的動(dòng)畫(huà)在顯著改變發(fā)生時(shí)重繪當(dāng)前幀(也就是關(guān)鍵幀)有咨,每幀之間剩下的繪制(可以通過(guò)關(guān)鍵幀推算出)將由熟練的藝術(shù)家來(lái)完成。CAKeyframeAnimation也是同樣的道理:你提供了顯著的幀蒸健,然后Core Animation在每幀之間進(jìn)行插入座享。

我們可以用之前使用顏色圖層的例子來(lái)演示,設(shè)置一個(gè)顏色的數(shù)組似忧,然后通過(guò)關(guān)鍵幀動(dòng)畫(huà)播放出來(lái)(清單8.5)

清單8.5 使用CAKeyframeAnimation應(yīng)用一系列顏色的變化

-?(IBAction)changeColor

{

//create a keyframe animation

CAKeyframeAnimation?*animation?=?[CAKeyframeAnimation?animation];

animation.keyPath?=?@"backgroundColor";

animation.duration?=?2.0;

animation.values?=?@[

(__bridge id)[UIColor?blueColor].CGColor,

(__bridge id)[UIColor?redColor].CGColor,

(__bridge id)[UIColor?greenColor].CGColor,

(__bridge id)[UIColor?blueColor].CGColor?];

//apply animation to layer

[self.colorLayer addAnimation:animation forKey:nil];

}

注意到序列中開(kāi)始和結(jié)束的顏色都是藍(lán)色渣叛,這是因?yàn)镃AKeyframeAnimation并不能自動(dòng)把當(dāng)前值作為第一幀(就像CABasicAnimation那樣把fromValue設(shè)為nil)。動(dòng)畫(huà)會(huì)在開(kāi)始的時(shí)候突然跳轉(zhuǎn)到第一幀的值盯捌,然后在動(dòng)畫(huà)結(jié)束的時(shí)候突然恢復(fù)到原始的值淳衙。所以為了動(dòng)畫(huà)的平滑特性,我們需要開(kāi)始和結(jié)束的關(guān)鍵幀來(lái)匹配當(dāng)前屬性的值饺著。

當(dāng)然可以創(chuàng)建一個(gè)結(jié)束和開(kāi)始值不同的動(dòng)畫(huà)滤祖,那樣的話就需要在動(dòng)畫(huà)啟動(dòng)之前手動(dòng)更新屬性和最后一幀的值保持一致,就和之前討論的一樣瓶籽。

我們用duration屬性把動(dòng)畫(huà)時(shí)間從默認(rèn)的0.25秒增加到2秒匠童,以便于動(dòng)畫(huà)做的不那么快。運(yùn)行它塑顺,你會(huì)發(fā)現(xiàn)動(dòng)畫(huà)通過(guò)顏色不斷循環(huán)汤求,但效果看起來(lái)有些奇怪。原因在于動(dòng)畫(huà)以一個(gè)恒定的步調(diào)在運(yùn)行严拒。當(dāng)在每個(gè)動(dòng)畫(huà)之間過(guò)渡的時(shí)候并沒(méi)有減速扬绪,這就產(chǎn)生了一個(gè)略微奇怪的效果,為了讓動(dòng)畫(huà)看起來(lái)更自然裤唠,我們需要調(diào)整一下緩沖挤牛,第十章將會(huì)詳細(xì)說(shuō)明。

提供一個(gè)數(shù)組的值就可以按照顏色變化做動(dòng)畫(huà)种蘸,但一般這不是直觀的方式去描述一段運(yùn)用墓赴。CAKeyframeAnimation有另一種方式去指定動(dòng)畫(huà)竞膳,就是使用CGPath。path屬性可以用一種直觀的方式诫硕,使用Core Graphics函數(shù)定義運(yùn)動(dòng)的序列來(lái)繪制動(dòng)畫(huà)坦辟。

我們來(lái)用一個(gè)宇宙飛船沿著一個(gè)簡(jiǎn)單曲線的實(shí)例演示一下。為了創(chuàng)建路徑章办,我們需要使用一個(gè)三次貝塞爾曲線锉走,它是一種使用開(kāi)始點(diǎn),結(jié)束點(diǎn)和另外兩個(gè)控制點(diǎn)來(lái)定義形狀的曲線藕届,可以通過(guò)使用一個(gè)基于C的Core Graphics繪圖指令來(lái)創(chuàng)建挪蹭,不過(guò)用UIKit提供的UIBezierPath類(lèi)會(huì)更簡(jiǎn)單。

我們這次用CAShapeLayer來(lái)在屏幕上繪制曲線休偶,盡管對(duì)動(dòng)畫(huà)來(lái)說(shuō)并不是必須的嚣潜,但這會(huì)讓我們的動(dòng)畫(huà)更加形象。繪制完CGPath之后椅贱,我們用它來(lái)創(chuàng)建一個(gè)CAKeyframeAnimation懂算,然后用它來(lái)應(yīng)用到我們的宇宙飛船。代碼見(jiàn)清單8.6庇麦,結(jié)果見(jiàn)圖8.1计技。

清單8.6 沿著一個(gè)貝塞爾曲線對(duì)圖層做動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create a path

UIBezierPath?*bezierPath?=?[[UIBezierPath?alloc]?init];

[bezierPath moveToPoint:CGPointMake(,?)];

[bezierPath addCurveToPoint:CGPointMake(,?)?controlPoint1:CGPointMake(,?)?controlPoint2:CGPointMake(,?)];

//draw the path using a CAShapeLayer

CAShapeLayer?*pathLayer?=?[CAShapeLayer?layer];

pathLayer.path?=?bezierPath.CGPath;

pathLayer.fillColor?=?[UIColor?clearColor].CGColor;

pathLayer.strokeColor?=?[UIColor?redColor].CGColor;

pathLayer.lineWidth?=?3.0f;

[self.containerView.layer addSublayer:pathLayer];

//add the ship

CALayer?*shipLayer?=?[CALayer?layer];

shipLayer.frame?=?CGRectMake(,?,?,?);

shipLayer.position?=?CGPointMake(,?);

shipLayer.contents?=?(__bridge id)[UIImage?imageNamed:?@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//create the keyframe animation

CAKeyframeAnimation?*animation?=?[CAKeyframeAnimation?animation];

animation.keyPath?=?@"position";

animation.duration?=?4.0;

animation.path?=?bezierPath.CGPath;

[shipLayer addAnimation:animation forKey:nil];

}

圖8.1 沿著一個(gè)貝塞爾曲線移動(dòng)的宇宙飛船圖片

運(yùn)行示例,你會(huì)發(fā)現(xiàn)飛船的動(dòng)畫(huà)有些不太真實(shí)山橄,這是因?yàn)楫?dāng)它運(yùn)動(dòng)的時(shí)候永遠(yuǎn)指向右邊垮媒,而不是指向曲線切線的方向。你可以調(diào)整它的affineTransform來(lái)對(duì)運(yùn)動(dòng)方向做動(dòng)畫(huà)航棱,但很可能和其它的動(dòng)畫(huà)沖突睡雇。

幸運(yùn)的是,蘋(píng)果預(yù)見(jiàn)到了這點(diǎn)饮醇,并且給CAKeyFrameAnimation添加了一個(gè)rotationMode的屬性它抱。設(shè)置它為常量kCAAnimationRotateAuto(清單8.7),圖層將會(huì)根據(jù)曲線的切線自動(dòng)旋轉(zhuǎn)(圖8.2)朴艰。

清單8.7 通過(guò)rotationMode自動(dòng)對(duì)齊圖層到曲線

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create a path

...

//create the keyframe animation

CAKeyframeAnimation?*animation?=?[CAKeyframeAnimation?animation];

animation.keyPath?=?@"position";

animation.duration?=?4.0;

animation.path?=?bezierPath.CGPath;

animation.rotationMode?=?kCAAnimationRotateAuto;

[shipLayer addAnimation:animation forKey:nil];

}

圖8.2 匹配曲線切線方向的飛船圖層

虛擬屬性

之前提到過(guò)屬性動(dòng)畫(huà)實(shí)際上是針對(duì)于關(guān)鍵路徑而不是一個(gè)鍵观蓄,這就意味著可以對(duì)子屬性甚至是虛擬屬性做動(dòng)畫(huà)。但是虛擬屬性到底是什么呢祠墅?

考慮一個(gè)旋轉(zhuǎn)的動(dòng)畫(huà):如果想要對(duì)一個(gè)物體做旋轉(zhuǎn)的動(dòng)畫(huà)侮穿,那就需要作用于transform屬性,因?yàn)镃ALayer沒(méi)有顯式提供角度或者方向之類(lèi)的屬性毁嗦,代碼如清單8.8所示

清單8.8 用transform屬性對(duì)圖層做動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//add the ship

CALayer?*shipLayer?=?[CALayer?layer];

shipLayer.frame?=?CGRectMake(,?,?,?);

shipLayer.position?=?CGPointMake(,?);

shipLayer.contents?=?(__bridge id)[UIImage?imageNamed:?@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//animate the ship rotation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"transform";

animation.duration?=?2.0;

animation.toValue?=?[NSValue?valueWithCATransform3D:?CATransform3DMakeRotation(M_PI,?,?,?)];

[shipLayer addAnimation:animation forKey:nil];

}

@end

這么做是可行的亲茅,但看起來(lái)更因?yàn)槭沁\(yùn)氣而不是設(shè)計(jì)的原因,如果我們把旋轉(zhuǎn)的值從M_PI(180度)調(diào)整到2 * M_PI(360度),然后運(yùn)行程序克锣,會(huì)發(fā)現(xiàn)這時(shí)候飛船完全不動(dòng)了茵肃。這是因?yàn)檫@里的矩陣做了一次360度的旋轉(zhuǎn),和做了0度是一樣的娶耍,所以最后的值根本沒(méi)變。

現(xiàn)在繼續(xù)使用M_PI饼酿,但這次用byValue而不是toValue榕酒。也許你會(huì)認(rèn)為這和設(shè)置toValue結(jié)果一樣,因?yàn)? + 90度 == 90度故俐,但實(shí)際上飛船的圖片變大了想鹰,并沒(méi)有做任何旋轉(zhuǎn),這是因?yàn)樽儞Q矩陣不能像角度值那樣疊加药版。

那么如果需要獨(dú)立于角度之外單獨(dú)對(duì)平移或者縮放做動(dòng)畫(huà)呢辑舷?由于都需要我們來(lái)修改transform屬性,實(shí)時(shí)地重新計(jì)算每個(gè)時(shí)間點(diǎn)的每個(gè)變換效果槽片,然后根據(jù)這些創(chuàng)建一個(gè)復(fù)雜的關(guān)鍵幀動(dòng)畫(huà)何缓,這一切都是為了對(duì)圖層的一個(gè)獨(dú)立做一個(gè)簡(jiǎn)單的動(dòng)畫(huà)。

幸運(yùn)的是还栓,有一個(gè)更好的解決方案:為了旋轉(zhuǎn)圖層碌廓,我們可以對(duì)transform.rotation關(guān)鍵路徑應(yīng)用動(dòng)畫(huà),而不是transform本身(清單8.9)剩盒。

清單8.9 對(duì)虛擬的transform.rotation屬性做動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//add the ship

CALayer?*shipLayer?=?[CALayer?layer];

shipLayer.frame?=?CGRectMake(,?,?,?);

shipLayer.position?=?CGPointMake(,?);

shipLayer.contents?=?(__bridge id)[UIImage?imageNamed:?@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//animate the ship rotation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"transform.rotation";

animation.duration?=?2.0;

animation.byValue?=?@(M_PI?*?);

[shipLayer addAnimation:animation forKey:nil];

}

@end

結(jié)果運(yùn)行的特別好谷婆,用transform.rotation而不是transform做動(dòng)畫(huà)的好處如下:

我們可以不通過(guò)關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動(dòng)畫(huà)。

可以用相對(duì)值而不是絕對(duì)值旋轉(zhuǎn)(設(shè)置byValue而不是toValue)辽聊。

可以不用創(chuàng)建CATransform3D纪挎,而是使用一個(gè)簡(jiǎn)單的數(shù)值來(lái)指定角度。

不會(huì)和transform.position或者transform.scale沖突(同樣是使用關(guān)鍵路徑來(lái)做獨(dú)立的動(dòng)畫(huà)屬性)跟匆。

transform.rotation屬性有一個(gè)奇怪的問(wèn)題是它其實(shí)并不存在异袄。這是因?yàn)镃ATransform3D并不是一個(gè)對(duì)象,它實(shí)際上是一個(gè)結(jié)構(gòu)體玛臂,也沒(méi)有符合KVC相關(guān)屬性隙轻,transform.rotation實(shí)際上是一個(gè)CALayer用于處理動(dòng)畫(huà)變換的虛擬屬性。

你不可以直接設(shè)置transform.rotation或者transform.scale垢揩,他們不能被直接使用玖绿。當(dāng)你對(duì)他們做動(dòng)畫(huà)時(shí),Core Animation自動(dòng)地根據(jù)通過(guò)CAValueFunction來(lái)計(jì)算的值來(lái)更新transform屬性叁巨。

CAValueFunction用于把我們賦給虛擬的transform.rotation簡(jiǎn)單浮點(diǎn)值轉(zhuǎn)換成真正的用于擺放圖層的CATransform3D矩陣值斑匪。你可以通過(guò)設(shè)置CAPropertyAnimation的valueFunction屬性來(lái)改變,于是你設(shè)置的函數(shù)將會(huì)覆蓋默認(rèn)的函數(shù)。

CAValueFunction看起來(lái)似乎是對(duì)那些不能簡(jiǎn)單相加的屬性(例如變換矩陣)做動(dòng)畫(huà)的非常有用的機(jī)制蚀瘸,但由于CAValueFunction的實(shí)現(xiàn)細(xì)節(jié)是私有的狡蝶,所以目前不能通過(guò)繼承它來(lái)自定義。你可以通過(guò)使用蘋(píng)果目前已近提供的常量(目前都是和變換矩陣的虛擬屬性相關(guān)贮勃,所以沒(méi)太多使用場(chǎng)景了贪惹,因?yàn)檫@些屬性都有了默認(rèn)的實(shí)現(xiàn)方式)。

動(dòng)畫(huà)組

CABasicAnimation和CAKeyframeAnimation僅僅作用于單獨(dú)的屬性寂嘉,而CAAnimationGroup可以把這些動(dòng)畫(huà)組合在一起奏瞬。CAAnimationGroup是另一個(gè)繼承于CAAnimation的子類(lèi),它添加了一個(gè)animations數(shù)組的屬性泉孩,用來(lái)組合別的動(dòng)畫(huà)硼端。我們把清單8.6那種關(guān)鍵幀動(dòng)畫(huà)和調(diào)整圖層背景色的基礎(chǔ)動(dòng)畫(huà)組合起來(lái)(清單8.10),結(jié)果如圖8.3所示寓搬。

清單8.10 組合關(guān)鍵幀動(dòng)畫(huà)和基礎(chǔ)動(dòng)畫(huà)

-?(void)viewDidLoad

{

[super?viewDidLoad];

//create a path

UIBezierPath?*bezierPath?=?[[UIBezierPath?alloc]?init];

[bezierPath moveToPoint:CGPointMake(,?)];

[bezierPath addCurveToPoint:CGPointMake(,?)?controlPoint1:CGPointMake(,?)?controlPoint2:CGPointMake(,?)];

//draw the path using a CAShapeLayer

CAShapeLayer?*pathLayer?=?[CAShapeLayer?layer];

pathLayer.path?=?bezierPath.CGPath;

pathLayer.fillColor?=?[UIColor?clearColor].CGColor;

pathLayer.strokeColor?=?[UIColor?redColor].CGColor;

pathLayer.lineWidth?=?3.0f;

[self.containerView.layer addSublayer:pathLayer];

//add a colored layer

CALayer?*colorLayer?=?[CALayer?layer];

colorLayer.frame?=?CGRectMake(,?,?,?);

colorLayer.position?=?CGPointMake(,?);

colorLayer.backgroundColor?=?[UIColor?greenColor].CGColor;

[self.containerView.layer addSublayer:colorLayer];

//create the position animation

CAKeyframeAnimation?*animation1?=?[CAKeyframeAnimation?animation];

animation1.keyPath?=?@"position";

animation1.path?=?bezierPath.CGPath;

animation1.rotationMode?=?kCAAnimationRotateAuto;

//create the color animation

CABasicAnimation?*animation2?=?[CABasicAnimation?animation];

animation2.keyPath?=?@"backgroundColor";

animation2.toValue?=?(__bridge id)[UIColor?redColor].CGColor;

//create group animation

CAAnimationGroup?*groupAnimation?=?[CAAnimationGroup?animation];

groupAnimation.animations?=?@[animation1,?animation2];

groupAnimation.duration?=?4.0;

//add the animation to the color layer

[colorLayer addAnimation:groupAnimation forKey:nil];

圖8.3 關(guān)鍵幀路徑和基礎(chǔ)動(dòng)畫(huà)的組合

過(guò)渡

有時(shí)候?qū)τ趇OS應(yīng)用程序來(lái)說(shuō)珍昨,希望能通過(guò)屬性動(dòng)畫(huà)來(lái)對(duì)比較難做動(dòng)畫(huà)的布局進(jìn)行一些改變。比如交換一段文本和圖片句喷,或者用一段網(wǎng)格視圖來(lái)替換镣典,等等。屬性動(dòng)畫(huà)只對(duì)圖層的可動(dòng)畫(huà)屬性起作用唾琼,所以如果要改變一個(gè)不能動(dòng)畫(huà)的屬性(比如圖片)骆撇,或者從層級(jí)關(guān)系中添加或者移除圖層,屬性動(dòng)畫(huà)將不起作用父叙。

于是就有了過(guò)渡的概念神郊。過(guò)渡并不像屬性動(dòng)畫(huà)那樣平滑地在兩個(gè)值之間做動(dòng)畫(huà),而是影響到整個(gè)圖層的變化趾唱。過(guò)渡動(dòng)畫(huà)首先展示之前的圖層外觀涌乳,然后通過(guò)一個(gè)交換過(guò)渡到新的外觀。

為了創(chuàng)建一個(gè)過(guò)渡動(dòng)畫(huà)甜癞,我們將使用CATransition夕晓,同樣是另一個(gè)CAAnimation的子類(lèi),和別的子類(lèi)不同悠咱,CAAnimation有一個(gè)type和subtype來(lái)標(biāo)識(shí)變換效果蒸辆。type屬性是一個(gè)NSString類(lèi)型,可以被設(shè)置成如下類(lèi)型:

kCATransitionFade

kCATransitionMoveIn

kCATransitionPush

kCATransitionReveal

到目前為止你只能使用上述四種類(lèi)型析既,但你可以通過(guò)一些別的方法來(lái)自定義過(guò)渡效果躬贡,后續(xù)會(huì)詳細(xì)介紹。

默認(rèn)的過(guò)渡類(lèi)型是kCATransitionFade眼坏,當(dāng)你在改變圖層屬性之后拂玻,就創(chuàng)建了一個(gè)平滑的淡入淡出效果。

我們?cè)诘谄哒碌睦又芯鸵呀?jīng)用到過(guò)kCATransitionPush,它創(chuàng)建了一個(gè)新的圖層檐蚜,從邊緣的一側(cè)滑動(dòng)進(jìn)來(lái)魄懂,把舊圖層從另一側(cè)推出去的效果。

kCATransitionMoveIn和kCATransitionReveal與kCATransitionPush類(lèi)似闯第,都實(shí)現(xiàn)了一個(gè)定向滑動(dòng)的動(dòng)畫(huà)市栗,但是有一些細(xì)微的不同,kCATransitionMoveIn從頂部滑動(dòng)進(jìn)入咳短,但不像推送動(dòng)畫(huà)那樣把老土層推走填帽,然而kCATransitionReveal把原始的圖層滑動(dòng)出去來(lái)顯示新的外觀,而不是把新的圖層滑動(dòng)進(jìn)入诲泌。

后面三種過(guò)渡類(lèi)型都有一個(gè)默認(rèn)的動(dòng)畫(huà)方向盲赊,它們都從左側(cè)滑入铣鹏,但是你可以通過(guò)subtype來(lái)控制它們的方向敷扫,提供了如下四種類(lèi)型:

kCATransitionFromRight

kCATransitionFromLeft

kCATransitionFromTop

kCATransitionFromBottom

一個(gè)簡(jiǎn)單的用CATransition來(lái)對(duì)非動(dòng)畫(huà)屬性做動(dòng)畫(huà)的例子如清單8.11所示,這里我們對(duì)UIImage的image屬性做修改诚卸,但是隱式動(dòng)畫(huà)或者CAPropertyAnimation都不能對(duì)它做動(dòng)畫(huà)葵第,因?yàn)镃ore Animation不知道如何在插圖圖片。通過(guò)對(duì)圖層應(yīng)用一個(gè)淡入淡出的過(guò)渡合溺,我們可以忽略它的內(nèi)容來(lái)做平滑動(dòng)畫(huà)(圖8.4)卒密,我們來(lái)嘗試修改過(guò)渡的type常量來(lái)觀察其它效果。

清單8.11 使用CATransition來(lái)對(duì)UIImageView做動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*imageView;

@property?(nonatomic,?copy)?NSArray?*images;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//set up images

self.images?=?@[[UIImage?imageNamed:@"Anchor.png"],

[UIImage?imageNamed:@"Cone.png"],

[UIImage?imageNamed:@"Igloo.png"],

[UIImage?imageNamed:@"Spaceship.png"]];

}

-?(IBAction)switchImage

{

//set up crossfade transition

CATransition?*transition?=?[CATransition?animation];

transition.type?=?kCATransitionFade;

//apply transition to imageview backing layer

[self.imageView.layer addAnimation:transition forKey:nil];

//cycle to next image

UIImage?*currentImage?=?self.imageView.image;

NSUInteger?index?=?[self.images indexOfObject:currentImage];

index?=?(index?+?)?%?[self.images count];

self.imageView.image?=?self.images[index];

}

@end

你可以從代碼中看出棠赛,過(guò)渡動(dòng)畫(huà)和之前的屬性動(dòng)畫(huà)或者動(dòng)畫(huà)組添加到圖層上的方式一致哮奇,都是通過(guò)-addAnimation:forKey:方法。但是和屬性動(dòng)畫(huà)不同的是睛约,對(duì)指定的圖層一次只能使用一次CATransition鼎俘,因此,無(wú)論你對(duì)動(dòng)畫(huà)的鍵設(shè)置什么值辩涝,過(guò)渡動(dòng)畫(huà)都會(huì)對(duì)它的鍵設(shè)置成“transition”贸伐,也就是常量kCATransition。?

圖8.4 使用CATransition對(duì)圖像平滑淡入淡出

隱式過(guò)渡

CATransision可以對(duì)圖層任何變化平滑過(guò)渡的事實(shí)使得它成為那些不好做動(dòng)畫(huà)的屬性圖層行為的理想候選怔揩。蘋(píng)果當(dāng)然意識(shí)到了這點(diǎn)捉邢,并且當(dāng)設(shè)置了CALayer的content屬性的時(shí)候,CATransition的確是默認(rèn)的行為商膊。但是對(duì)于視圖關(guān)聯(lián)的圖層伏伐,或者是其他隱式動(dòng)畫(huà)的行為,這個(gè)特性依然是被禁用的晕拆,但是對(duì)于你自己創(chuàng)建的圖層秘案,這意味著對(duì)圖層contents圖片做的改動(dòng)都會(huì)自動(dòng)附上淡入淡出的動(dòng)畫(huà)。

我們?cè)诘谄哒率褂肅ATransition作為一個(gè)圖層行為來(lái)改變圖層的背景色,當(dāng)然backgroundColor屬性可以通過(guò)正常的CAPropertyAnimation來(lái)實(shí)現(xiàn)阱高,但這不是說(shuō)不可以用CATransition來(lái)實(shí)行赚导。

對(duì)圖層樹(shù)的動(dòng)畫(huà)

CATransition并不作用于指定的圖層屬性,這就是說(shuō)你可以在即使不能準(zhǔn)確得知改變了什么的情況下對(duì)圖層做動(dòng)畫(huà)赤惊,例如吼旧,在不知道UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它未舟,或者在不知道UIViewController內(nèi)部的視圖層級(jí)的情況下對(duì)兩個(gè)不同的實(shí)例做過(guò)渡動(dòng)畫(huà)圈暗。

這些例子和我們之前所討論的情況完全不同,因?yàn)樗鼈儾粌H涉及到圖層的屬性裕膀,而且是整個(gè)圖層樹(shù)的改變--我們?cè)谶@種動(dòng)畫(huà)的過(guò)程中手動(dòng)在層級(jí)關(guān)系中添加或者移除圖層员串。

這里用到了一個(gè)小詭計(jì),要確保CATransition添加到的圖層在過(guò)渡動(dòng)畫(huà)發(fā)生時(shí)不會(huì)在樹(shù)狀結(jié)構(gòu)中被移除昼扛,否則CATransition將會(huì)和圖層一起被移除寸齐。一般來(lái)說(shuō),你只需要將動(dòng)畫(huà)添加到被影響圖層的superlayer抄谐。

在清單8.2中渺鹦,我們展示了如何在UITabBarController切換標(biāo)簽的時(shí)候添加淡入淡出的動(dòng)畫(huà)。這里我們建立了默認(rèn)的標(biāo)簽應(yīng)用程序模板蛹含,然后用UITabBarControllerDelegate的-tabBarController:didSelectViewController:方法來(lái)應(yīng)用過(guò)渡動(dòng)畫(huà)毅厚。我們把動(dòng)畫(huà)添加到UITabBarController的視圖圖層上,于是在標(biāo)簽被替換的時(shí)候動(dòng)畫(huà)不會(huì)被移除浦箱。

清單8.12 對(duì)UITabBarController做動(dòng)畫(huà)

#import "AppDelegate.h"

#import "FirstViewController.h"

#import "SecondViewController.h"

#import @implementation AppDelegate

-?(BOOL)application:(UIApplication?*)application didFinishLaunchingWithOptions:(NSDictionary?*)launchOptions

{

self.window?=?[[UIWindow?alloc]?initWithFrame:?[[UIScreen?mainScreen]?bounds]];

UIViewController?*viewController1?=?[[FirstViewController?alloc]?init];

UIViewController?*viewController2?=?[[SecondViewController?alloc]?init];

self.tabBarController?=?[[UITabBarController?alloc]?init];

self.tabBarController.viewControllers?=?@[viewController1,?viewController2];

self.tabBarController.delegate?=?self;

self.window.rootViewController?=?self.tabBarController;

[self.window makeKeyAndVisible];

return?YES;

}

-?(void)tabBarController:(UITabBarController?*)tabBarController didSelectViewController:(UIViewController?*)viewController

{

?//set up crossfade transition

CATransition?*transition?=?[CATransition?animation];

transition.type?=?kCATransitionFade;

//apply transition to tab bar controller's view

[self.tabBarController.view.layer addAnimation:transition forKey:nil];

}

自定義動(dòng)畫(huà)

我們證實(shí)了過(guò)渡是一種對(duì)那些不太好做平滑動(dòng)畫(huà)屬性的強(qiáng)大工具吸耿,但是CATransition的提供的動(dòng)畫(huà)類(lèi)型太少了。

更奇怪的是蘋(píng)果通過(guò)UIView +transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:方法提供了Core Animation的過(guò)渡特性酷窥。但是這里的可用的過(guò)渡選項(xiàng)和CATransition的type屬性提供的常量完全不同咽安。UIView過(guò)渡方法中options參數(shù)可以由如下常量指定:

UIViewAnimationOptionTransitionFlipFromLeft

UIViewAnimationOptionTransitionFlipFromRight

UIViewAnimationOptionTransitionCurlUp

UIViewAnimationOptionTransitionCurlDown

UIViewAnimationOptionTransitionCrossDissolve

UIViewAnimationOptionTransitionFlipFromTop

UIViewAnimationOptionTransitionFlipFromBottom

除了UIViewAnimationOptionTransitionCrossDissolve之外,剩下的值和CATransition類(lèi)型完全沒(méi)關(guān)系竖幔。你可以用之前例子修改過(guò)的版本來(lái)測(cè)試一下(見(jiàn)清單8.13)板乙。

清單8.13 使用UIKit提供的方法來(lái)做過(guò)渡動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*imageView;

@property?(nonatomic,?copy)?NSArray?*images;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];?//set up images

self.images?=?@[[UIImage?imageNamed:@"Anchor.png"],

[UIImage?imageNamed:@"Cone.png"],

[UIImage?imageNamed:@"Igloo.png"],

[UIImage?imageNamed:@"Spaceship.png"]];

-?(IBAction)switchImage

{

[UIView?transitionWithView:self.imageView duration:1.0

options:UIViewAnimationOptionTransitionFlipFromLeft

animations:^{

//cycle to next image

UIImage?*currentImage?=?self.imageView.image;

NSUInteger?index?=?[self.images indexOfObject:currentImage];

index?=?(index?+?)?%?[self.images count];

self.imageView.image?=?self.images[index];

}

completion:NULL];

}

@end

文檔暗示過(guò)在iOS5(帶來(lái)了Core Image框架)之后,可以通過(guò)CATransition的filter屬性拳氢,用CIFilter來(lái)創(chuàng)建其它的過(guò)渡效果募逞。然是直到iOS6都做不到這點(diǎn)。試圖對(duì)CATransition使用Core Image的濾鏡完全沒(méi)效果(但是在Mac OS中是可行的馋评,也許文檔是想表達(dá)這個(gè)意思)放接。

因此,根據(jù)要實(shí)現(xiàn)的效果留特,你只用關(guān)心是用CATransition還是用UIView的過(guò)渡方法就可以了纠脾。希望下個(gè)版本的iOS系統(tǒng)可以通過(guò)CATransition很好的支持Core Image的過(guò)渡濾鏡效果(或許甚至?xí)行碌姆椒ǎ?/p>

但這并不意味著在iOS上就不能實(shí)現(xiàn)自定義的過(guò)渡效果了玛瘸。這只是意味著你需要做一些額外的工作。就像之前提到的那樣苟蹈,過(guò)渡動(dòng)畫(huà)做基礎(chǔ)的原則就是對(duì)原始的圖層外觀截圖糊渊,然后添加一段動(dòng)畫(huà),平滑過(guò)渡到圖層改變之后那個(gè)截圖的效果慧脱。如果我們知道如何對(duì)圖層截圖渺绒,我們就可以使用屬性動(dòng)畫(huà)來(lái)代替CATransition或者是UIKit的過(guò)渡方法來(lái)實(shí)現(xiàn)動(dòng)畫(huà)。

事實(shí)證明菱鸥,對(duì)圖層做截圖還是很簡(jiǎn)單的宗兼。CALayer有一個(gè)-renderInContext:方法,可以通過(guò)把它繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片氮采,然后在另外的視圖中顯示出來(lái)殷绍。如果我們把這個(gè)截屏視圖置于原始視圖之上,就可以遮住真實(shí)視圖的所有變化鹊漠,于是重新創(chuàng)建了一個(gè)簡(jiǎn)單的過(guò)渡效果主到。

清單8.14演示了一個(gè)基本的實(shí)現(xiàn)。我們對(duì)當(dāng)前視圖狀態(tài)截圖贸呢,然后在我們改變?cè)家晥D的背景色的時(shí)候?qū)貓D快速轉(zhuǎn)動(dòng)并且淡出镰烧,圖8.5展示了我們自定義的過(guò)渡效果拢军。

為了讓事情更簡(jiǎn)單楞陷,我們用UIView -animateWithDuration:completion:方法來(lái)實(shí)現(xiàn)。雖然用CABasicAnimation可以達(dá)到同樣的效果茉唉,但是那樣的話我們就需要對(duì)圖層的變換和不透明屬性創(chuàng)建單獨(dú)的動(dòng)畫(huà)固蛾,然后當(dāng)動(dòng)畫(huà)結(jié)束的是哦戶在CAAnimationDelegate中把coverView從屏幕中移除。

清單8.14 用renderInContext:創(chuàng)建自定義過(guò)渡效果

@implementation?ViewController

-?(IBAction)performTransition

{

//preserve the current view snapshot

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size,?YES,?0.0);

[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage?*coverImage?=?UIGraphicsGetImageFromCurrentImageContext();

//insert snapshot view in front of this one

UIView?*coverView?=?[[UIImageView?alloc]?initWithImage:coverImage];

coverView.frame?=?self.view.bounds;

[self.view addSubview:coverView];

//update the view (we'll simply randomize the layer background color)

CGFloat?red?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?green?=?arc4random()?/?(CGFloat)INT_MAX;

CGFloat?blue?=?arc4random()?/?(CGFloat)INT_MAX;

self.view.backgroundColor?=?[UIColor?colorWithRed:red green:green blue:blue alpha:1.0];

//perform animation (anything you like)

[UIView?animateWithDuration:1.0?animations:^{

//scale, rotate and fade the view

CGAffineTransform?transform?=?CGAffineTransformMakeScale(0.01,?0.01);

transform?=?CGAffineTransformRotate(transform,?M_PI_2);

coverView.transform?=?transform;

coverView.alpha?=?0.0;

}?completion:^(BOOL finished)?{

//remove the cover view now we're finished with it

[coverView removeFromSuperview];

}];

}

@end

圖8.5 使用renderInContext:創(chuàng)建自定義過(guò)渡效果

這里有個(gè)警告:-renderInContext:捕獲了圖層的圖片和子圖層度陆,但是不能對(duì)子圖層正確地處理變換效果艾凯,而且對(duì)視頻和OpenGL內(nèi)容也不起作用。但是用CATransition懂傀,或者用私有的截屏方式就沒(méi)有這個(gè)限制了趾诗。

在動(dòng)畫(huà)過(guò)程中取消動(dòng)畫(huà)

之前提到過(guò),你可以用-addAnimation:forKey:方法中的key參數(shù)來(lái)在添加動(dòng)畫(huà)之后檢索一個(gè)動(dòng)畫(huà)蹬蚁,使用如下方法:

-?(CAAnimation?*)animationForKey:(NSString?*)key;

但并不支持在動(dòng)畫(huà)運(yùn)行過(guò)程中修改動(dòng)畫(huà)恃泪,所以這個(gè)方法主要用來(lái)檢測(cè)動(dòng)畫(huà)的屬性,或者判斷它是否被添加到當(dāng)前圖層中犀斋。

為了終止一個(gè)指定的動(dòng)畫(huà)贝乎,你可以用如下方法把它從圖層移除掉:

-?(void)removeAnimationForKey:(NSString?*)key;

或者移除所有動(dòng)畫(huà):

-?(void)removeAllAnimations;

動(dòng)畫(huà)一旦被移除,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值叽粹。一般說(shuō)來(lái)览效,動(dòng)畫(huà)在結(jié)束之后被自動(dòng)移除却舀,除非設(shè)置removedOnCompletion為NO,如果你設(shè)置動(dòng)畫(huà)在結(jié)束之后不被自動(dòng)移除锤灿,那么當(dāng)它不需要的時(shí)候你要手動(dòng)移除它挽拔;否則它會(huì)一直存在于內(nèi)存中,直到圖層被銷(xiāo)毀但校。

我們來(lái)擴(kuò)展之前旋轉(zhuǎn)飛船的示例篱昔,這里添加一個(gè)按鈕來(lái)停止或者啟動(dòng)動(dòng)畫(huà)。這一次我們用一個(gè)非nil的值作為動(dòng)畫(huà)的鍵始腾,以便之后可以移除它州刽。-animationDidStop:finished:方法中的flag參數(shù)表明了動(dòng)畫(huà)是自然結(jié)束還是被打斷,我們可以在控制臺(tái)打印出來(lái)浪箭。如果你用停止按鈕來(lái)終止動(dòng)畫(huà)穗椅,它會(huì)打印NO,如果允許它完成奶栖,它會(huì)打印YES匹表。

清單8.15是更新后的示例代碼,圖8.6顯示了結(jié)果宣鄙。

清單8.15 開(kāi)始和停止一個(gè)動(dòng)畫(huà)

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@property?(nonatomic,?strong)?CALayer?*shipLayer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[super?viewDidLoad];

//add the ship

self.shipLayer?=?[CALayer?layer];

self.shipLayer.frame?=?CGRectMake(,?,?,?);

self.shipLayer.position?=?CGPointMake(,?);

self.shipLayer.contents?=?(__bridge id)[UIImage?imageNamed:?@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:self.shipLayer];

}

-?(IBAction)start

{

//animate the ship rotation

CABasicAnimation?*animation?=?[CABasicAnimation?animation];

animation.keyPath?=?@"transform.rotation";

animation.duration?=?2.0;

animation.byValue?=?@(M_PI?*?);

animation.delegate?=?self;

[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];

}

-?(IBAction)stop

{

[self.shipLayer removeAnimationForKey:@"rotateAnimation"];

}

-?(void)animationDidStop:(CAAnimation?*)anim finished:(BOOL)flag

{

//log that the animation stopped

NSLog(@"The animation stopped (finished: %@)",?flag??@"YES":?@"NO");

}

圖8.6 通過(guò)開(kāi)始和停止按鈕控制的旋轉(zhuǎn)動(dòng)畫(huà)

總結(jié)

這一章中袍镀,我們涉及了屬性動(dòng)畫(huà)(你可以對(duì)單獨(dú)的圖層屬性動(dòng)畫(huà)有更加具體的控制),動(dòng)畫(huà)組(把多個(gè)屬性動(dòng)畫(huà)組合成一個(gè)獨(dú)立單元)以及過(guò)度(影響整個(gè)圖層冻晤,可以用來(lái)對(duì)圖層的任何內(nèi)容做任何類(lèi)型的動(dòng)畫(huà)苇羡,包括子圖層的添加和移除)。?

原文:https://www.bbsmax.com/A/D8547yv3JE/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鼻弧,一起剝皮案震驚了整個(gè)濱河市设江,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攘轩,老刑警劉巖叉存,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異度帮,居然都是意外死亡歼捏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)笨篷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瞳秽,“玉大人,你說(shuō)我怎么就攤上這事冕屯〖庞眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵安聘,是天一觀的道長(zhǎng)痰洒。 經(jīng)常有香客問(wèn)我瓢棒,道長(zhǎng),這世上最難降的妖魔是什么丘喻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任脯宿,我火速辦了婚禮,結(jié)果婚禮上泉粉,老公的妹妹穿的比我還像新娘连霉。我一直安慰自己,他們只是感情好嗡靡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布跺撼。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躲舌,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音卫枝,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菩貌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼重荠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼箭阶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晚缩,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尾膊,失蹤者是張志新(化名)和其女友劉穎媳危,沒(méi)想到半個(gè)月后荞彼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡待笑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年鸣皂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暮蹂。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寞缝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仰泻,到底是詐尸還是另有隱情荆陆,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布集侯,位于F島的核電站被啼,受9級(jí)特大地震影響帜消,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浓体,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一泡挺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧命浴,春花似錦娄猫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至碍讯,卻和暖如春褂删,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冲茸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工屯阀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轴术。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓难衰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逗栽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盖袭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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