iOS核心動畫高級技巧--(八)顯式動畫

上一章介紹了隱式動畫的概念骑祟。隱式動畫是在iOS平臺創(chuàng)建動態(tài)用戶界面的一種直接方式涣澡,也是UIKit動畫機制的基礎(chǔ)狐肢,不過它并不能涵蓋所有的動畫類型惠爽。在這一章 中癌蓖,我們將要研究一下顯式動畫,它能夠?qū)σ恍傩宰鲋付ǖ淖远x動畫婚肆,或者創(chuàng)建非線性動畫费坊,比如沿著任意一條曲線移動。

屬性動畫

CAAnimationDelegate在任何頭文件中都找不到旬痹,但是可以在CAAnimation頭文件或者蘋果開發(fā)者文檔中找到相關(guān)函數(shù)附井。在這個例子中,我們用- animationDidStop: finished: 方法在動畫結(jié)束之后來更新圖層backgroundColor的两残。

當(dāng)更新屬性的時候永毅,我們需要設(shè)置一個新的事務(wù),并且禁用圖層行為人弓。否則動畫會發(fā)生兩次沼死,一個是因為顯式的 CABasicAnimation ,另一次是因為隱式動畫崔赌,具體實現(xiàn)代碼如下意蛀。

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 100, 100);
    colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
    colorLayer.backgroundColor = [UIColor redColor].CGColor;
    self.colorLayer = colorLayer;
    [self.view.layer addSublayer:colorLayer];
}

- (IBAction)changeColor:(id)sender {
    
    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 *baseAnimation = [CABasicAnimation animation];
    baseAnimation.keyPath = @"backgroundColor";
    baseAnimation.toValue = (__bridge id)color.CGColor;
    baseAnimation.delegate = self;
    [self.colorLayer addAnimation:baseAnimation forKey:nil];
    
}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
    [CATransaction begin];
    [CATransaction setDisableActions:true];
    [CATransaction setDisableActions:0.25];
    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
}

CAAnimation而言,使用委托模式而不是一個完成塊會帶來一個問題健芭,就是當(dāng) 你有多個動畫的時候县钥,無法在在回調(diào)方法中區(qū)分。在一個視圖控制器中創(chuàng)建動畫的 時候慈迈,通常會用控制器本身作為一個委托(如上面所示)若贮,但是所有的動畫都會調(diào)用同一個回調(diào)方法,所以你就需要判斷到底是那個圖層的調(diào)用。

動畫本身會作為一個參數(shù)傳入委托的方法谴麦,也許你會認(rèn)為可以控制器中把動畫存儲
為一個屬性蠢沿,然后在回調(diào)用比較,但實際上并不起作用匾效,因為委托傳入的動畫參數(shù)
是原始值的一個深拷貝舷蟀,從而不是同一個值。

當(dāng)使用-animation:forKey:把動畫添加到圖層, 這里有一個到目前為止我們都設(shè)置為nilkey參數(shù)面哼。這里的鍵是-animationForKey: 方法找到對應(yīng)動 畫的唯一標(biāo)識符野宜,而當(dāng)前動畫的所有鍵都可以用animationKeys 獲取。如果我們 對每個動畫都關(guān)聯(lián)一個唯一的鍵精绎,就可以對每個圖層循環(huán)所有鍵速缨,然后調(diào)用 - animationForKey:來比對結(jié)果锌妻。盡管這不是一個優(yōu)雅的實現(xiàn)代乃。

幸運的是,還有一種更加簡單的方法仿粹。像所有的 NSObject子類一 樣搁吓,CAAnimation實現(xiàn)了KVC(鍵-值-編碼)協(xié)議,于是你可以用 - setValue:forKey:- valueForKey:方法來存取屬性吭历。但是CAAnimation有 一個不同的性能:它更像一個NSDictionary堕仔,可以讓你隨意設(shè)置鍵值對,即使和你使用的動畫類所聲明的屬性并不匹配晌区。

這意味著你可以對動畫用任意類型打標(biāo)簽摩骨。在這里,我們給UIView類型的指針添 加的動畫朗若,所以可以簡單地判斷動畫到底屬于哪個視圖恼五,然后在委托方法中用這個 信息正確地更新鐘的指針。

在模擬器上運行的很好哭懈,但當(dāng)真 正跑在iOS設(shè)備上時灾馒,我們發(fā)現(xiàn)在 -animationDidStop:finished: 委托方法調(diào)用 之前,指針會迅速返回到原始值遣总。

問題在于回調(diào)方法在動畫完成之前已經(jīng)被調(diào)用了睬罗,但不能保證這發(fā)生在屬性動畫返
回初始狀態(tài)之前。這同時也很好地說明了為什么要在真實的設(shè)備上測試動畫代碼旭斥,
而不僅僅是模擬器容达。

我們可以用一個 fillMode 屬性來解決這個問題,下一章會詳細(xì)說明垂券,這里知道在 動畫之前設(shè)置它比在動畫結(jié)束之后更新屬性更加方便董饰。

關(guān)鍵幀動畫

CABasicAnimation揭示了大多數(shù)隱式動畫背后依賴的機制,這的確很有趣,但是顯示地給圖層添加CABasicAnimation相較于隱式動畫而言卒暂,只能說費力不討好啄栓。

CAKeyframeAnimation是另一種UIKit沒有暴露出來但功能強大的類。和CABasicAnimation 類似也祠, CAKeyframeAnimation 同樣是CAPropertyAnimation的一個子類昙楚,它依然作用于單一的一個屬性,但是和CABasicAnimation 不一樣的是诈嘿,它不限制于設(shè)置一個起始和結(jié)束的值堪旧,而是可以根據(jù)一連串隨意的值來做動畫。

關(guān)鍵幀起源于傳動動畫奖亚,意思是指主導(dǎo)的動畫在顯著改變發(fā)生時重繪當(dāng)前幀(也就 是關(guān)鍵幀)淳梦,每幀之間剩下的繪制(可以通過關(guān)鍵幀推算出)將由熟練的藝術(shù)家來 完成。 CAKeyframeAnimation 也是同樣的道理:你提供了顯著的幀昔字,然后Core Animation在每幀之間進(jìn)行插入爆袍。

我們可以用之前使用顏色圖層的例子來演示,設(shè)置一個顏色的數(shù)組作郭,然后通過關(guān)鍵 幀動畫播放出來

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 100, 100);
    colorLayer.backgroundColor = [UIColor redColor].CGColor;
    colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
    self.colorLayer = colorLayer;
    [self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(id)sender {
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"backgroundColor";
    keyAnimation.duration = 2.0;
    keyAnimation.values = @[(__bridge id)[UIColor blueColor].CGColor,
                            (__bridge id)[UIColor redColor].CGColor,
                            (__bridge id)[UIColor greenColor].CGColor,
                            (__bridge id)[UIColor blueColor].CGColor
                            ];
    [self.colorLayer addAnimation:keyAnimation forKey:nil];
}

注意到序列中開始和結(jié)束的顏色都是藍(lán)色陨囊,這是因為CAKeyframeAnimation并不 能自動把當(dāng)前值作為第一幀(就像CABasicAnimation 那樣把fromValue設(shè)為 nil )。動畫會在開始的時候突然跳轉(zhuǎn)到第一幀的值夹攒,然后在動畫結(jié)束的時候 突然恢復(fù)到原始的值蜘醋。所以為了動畫的平滑特性,我們需要開始和結(jié)束的關(guān)鍵幀來 匹配當(dāng)前屬性的值咏尝。

當(dāng)然可以創(chuàng)建一個結(jié)束和開始值不同的動畫压语,那樣的話就需要在動畫啟動之前手動更新屬性和最后一幀的值保持一致,就和之前討論的一樣编检。

我們用duration屬性把動畫時間從默認(rèn)的0.25秒增加到2秒胎食,以便于動畫做的不 那么快。運行它蒙谓,你會發(fā)現(xiàn)動畫通過顏色不斷循環(huán)斥季,但效果看起來有些奇怪。原因 在于動畫以一個恒定的步調(diào)在運行累驮。當(dāng)在每個動畫之間過渡的時候并沒有減速酣倾,這 就產(chǎn)生了一個略微奇怪的效果,為了讓動畫看起來更自然谤专,我們需要調(diào)整一下緩 沖躁锡,第十章將會詳細(xì)說明。

提供一個數(shù)組的值就可以按照顏色變化做動畫置侍,但一般來說用數(shù)組來描述動畫運動并不直觀映之。
CAKeyframeAnimation有另一種方式去指定動畫拦焚,就是使用CGPathpath屬性可以用一種直觀的方式杠输,使用Core Graphics函數(shù)定義運動序列來繪制動畫赎败。

我們來用一個宇宙飛船沿著一個簡單曲線的實例演示一下。為了創(chuàng)建路徑蠢甲,我們需要使用一個三次貝塞爾曲線僵刮,它是一種使用開始點,結(jié)束點和另外兩個控制點來定義形狀的曲線鹦牛,可以通過使用一個基于CCore Graphics繪圖指令來創(chuàng)建搞糕,不過用UIKit提供的 UIBezierPath類會更簡單。
我們這次用CAShapeLayer來在屏幕上繪制曲線曼追,盡管對動畫來說并不是必須 的窍仰,但這會讓我們的動畫更加形象。繪制完 CGPath之后礼殊,我們用它來創(chuàng)建一 個CAKeyframeAnimation差导,然后用它來應(yīng)用到我們的宇宙飛船蹂空。

運行示例刚夺,你會發(fā)現(xiàn)飛船的動畫有些不太真實屹蚊,這是因為當(dāng)它運動的時候永遠(yuǎn)指向 右邊悟民,而不是指向曲線切線的方向坝辫。你可以調(diào)整它的 affineTransform 來對運動 方向做動畫,但很可能和其它的動畫沖突射亏。

蘋果預(yù)見到了這點近忙,并且給CAKeyFrameAnimation添加了一個rotationMode 的屬性。設(shè)置它為常量KCAAnimationRotateAuto智润,圖層將會根據(jù)曲線的切線自動旋轉(zhuǎn)及舍。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 50)];
    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];
    
    CALayer *shipLayer = [CALayer layer];
    shipLayer.frame = CGRectMake(0, 0, 64, 64);
    shipLayer.position = CGPointMake(0, 150);
    shipLayer.contents = (__bridge id)[UIImage imageNamed:@"ship.png"].CGImage;
    [self.containerView.layer addSublayer:shipLayer];

    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"position";
    keyAnimation.duration = 4.0;
    keyAnimation.path = bezierPath.CGPath;
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
    [shipLayer addAnimation:keyAnimation forKey:nil];
}
虛擬屬性

之前提到過屬性動畫實際上是針對于關(guān)鍵路徑而不是一個鍵,這就意味著可以對子屬性甚至是虛擬屬性做動畫窟绷。但是虛擬屬性到底是什么呢?
考慮一個旋轉(zhuǎn)的動畫:如果想要對一個物體做旋轉(zhuǎn)的動畫锯玛,那就需要作用
transform 屬性,因為 CALayer沒有顯式提供角度或者方向之類的屬性兼蜈,代 碼如下所示

transform.rotation 而不是 transform 做動畫的好處 如下:

  • 我們可以不通過關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動畫攘残。
  • 可以用相對值而不是絕對值旋轉(zhuǎn)(設(shè)置 byValue而不是toValue )。
  • 可以不用創(chuàng)建CATransform3D 为狸,而是使用一個簡單的數(shù)值來指定角度歼郭。
  • 不會和transform.position或者transform.scale沖突(同樣是使用關(guān)鍵路徑來做獨立的動畫屬性)。

transform.rotation屬性有一個奇怪的問題是它其實并不存在辐棒。這是因為CATransform3D 并不是一個對象病曾,它實際上是一個結(jié)構(gòu)體牍蜂,也沒有符合KVC相關(guān)屬性,transform.rotation實際上是一個 CALayer用于處理動畫變換的虛 擬屬性泰涂。

你不可以直接設(shè)置 transform.rotation 或者 transform.scale 鲫竞,他們不能被直接使用。當(dāng)你對他們做動畫時逼蒙,Core Animation自動地根據(jù)通過 CAValueFunction來計算的值來更新 transform屬性贡茅。

CAValueFunction 用于把我們賦給虛擬的transfrom.rotation 簡單浮點值轉(zhuǎn)換成真正的用于擺放圖層的CATransform3D矩陣值。你可以通過設(shè)置CAPropertyAnimationvalueFunction屬性來改變其做,于是你設(shè)置的函數(shù)將會覆蓋默認(rèn)的函數(shù)顶考。

CAValueFunction 看起來似乎是對那些不能簡單相加的屬性(例如變換矩陣)做動畫的非常有用的機制,但由于 CAValueFunction 的實現(xiàn)細(xì)節(jié)是私有的妖泄,所以目 前不能通過繼承它來自定義驹沿。你可以通過使用蘋果目前已經(jīng)提供的常量(目前都是 和變換矩陣的虛擬屬性相關(guān),所以沒太多使用場景了蹈胡,因為這些屬性都有了默認(rèn)的 實現(xiàn)方式)渊季。

動畫組

CABasicAnimationCAKeyframeAnimation 僅僅作用于單獨的屬性,而CAAnimationGroup可以把這些動畫組合在一起罚渐。 CAAnimationGroup是另一個繼承于CAAnimation 的子類却汉,它添加了一個 animations數(shù)組的屬性,用來組合別的動畫荷并。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 150)];
    
    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];
    
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 64, 64);
    colorLayer.position = CGPointMake(0, 150);
    colorLayer.backgroundColor = [UIColor greenColor].CGColor;
    [self.containerView.layer addSublayer:colorLayer];
    
    
    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"position";
    keyAnimation.path = bezierPath.CGPath;
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
    
    CABasicAnimation *baseAnimation = [CABasicAnimation animation];
    baseAnimation.keyPath = @"backgroundColor";
    baseAnimation.toValue = (__bridge id)[UIColor redColor].CGColor;
    
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[keyAnimation, baseAnimation];
    groupAnimation.duration = 4.0;
    groupAnimation.autoreverses = true;
    [colorLayer addAnimation:groupAnimation forKey:nil];
}
過渡

有時候?qū)τ?code>iOS應(yīng)用程序來說合砂,希望能通過屬性動畫來對比較難做動畫的布局進(jìn)行 一些改變。比如交換一段文本和圖片源织,或者用一段網(wǎng)格視圖來替換翩伪,等等。屬性動畫只對圖層的可動畫屬性起作用谈息,所以如果要改變一個不能動畫的屬性(比如圖 片)缘屹,或者從層級關(guān)系中添加或者移除圖層,屬性動畫將不起作用侠仇。

于是就有了過渡的概念轻姿。過渡并不像屬性動畫那樣平滑地在兩個值之間做動畫,而是影響到整個圖層的變化逻炊。過渡動畫首先展示之前的圖層外觀互亮,然后通過一個交換過渡到新的外觀。

為了創(chuàng)建一個過渡動畫嗅骄,我們將使用CATransition胳挎,同樣是另一個CAAnimation的子類,和別的子類不同,CATransition 有一 個typesubtype來標(biāo)識變換效果。type屬性是一個NSString類型,可以被設(shè)置成如下類型:

kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal

到目前為止你只能使用上述四種類型溺森,但你可以通過一些別的方法來自定義過渡效果慕爬,后續(xù)會詳細(xì)介紹窑眯。

默認(rèn)的過渡類型是 kCATransitionFade,當(dāng)你在改變圖層屬性之后医窿,就創(chuàng)建了一 個平滑的淡入淡出效果磅甩。

我們在第七章的例子中就已經(jīng)用到過 kCATransitionPush,它創(chuàng)建了一個新的圖 層姥卢,從邊緣的一側(cè)滑動進(jìn)來卷要,把舊圖層從另一側(cè)推出去的效果。

kCATransitionMoveInkCATransitionRevealkCATransitionPush類 似独榴,都實現(xiàn)了一個定向滑動的動畫僧叉,但是有一些細(xì)微的不同,kCATransitionMoveIn從頂部滑動進(jìn)入棺榔,但不像推送動畫那樣把老土層推走瓶堕,然而kCATransitionReveal把原始的圖層滑動出去來顯示新的外觀,而不是把新的圖層滑動進(jìn)入症歇。

后面三種過渡類型都有一個默認(rèn)的動畫方向郎笆,它們都從左側(cè)滑入,但是你可以通 過 subtype來控制它們的方向忘晤,提供了如下四種類型:

kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom

一個簡單的用CATransition來對非動畫屬性做動畫的例子宛蚓,這里我們對UIImageimage屬性做修改,但是隱式動畫或者CAPropertyAnimation都不能對它做動畫设塔,因為Core Animation不知道如何在插圖圖片凄吏。通過對圖層應(yīng)用一個淡入淡出的過渡,我們可以忽略它的內(nèi)容來做平滑動畫壹置,我們來嘗試修改過渡的 type常量來觀察其它效果竞思。

使用 CATransition 來對UIImageView 做動畫

- (void)viewDidLoad {
    [super viewDidLoad];
    self.images = @[
                    [UIImage imageNamed:@"Anchor.png"],
                    [UIImage imageNamed:@"Cone.png"],
                    [UIImage imageNamed:@"Igloo.png"],
                    [UIImage imageNamed:@"Spaceship.png"]
                    ];
}
- (IBAction)changeImage:(id)sender {
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    [self.imageView.layer addAnimation:transition forKey:nil];
    
    UIImage *currentImage = self.imageView.image;
    NSUInteger index = [self.images indexOfObject:currentImage];
    index = (index + 1) % [self.images count];
    self.imageView.image = self.images[index];
}

效果如下:

效果.gif

你可以從代碼中看出表谊,過渡動畫和之前的屬性動畫或者動畫組添加到圖層上的方式一致钞护,都是通過- addAnimation: forKey:方法。但是和屬性動畫不同的是爆办,對指定的圖層一次只能使用一次 CATransition难咕,因此,無論你對動畫的鍵設(shè)置什么值距辆,過渡動畫都會對它的鍵設(shè)成“transition”余佃,也就是常量 kCATransition 。

隱式過渡

CATransision 可以對圖層任何變化平滑過渡的事實使得它成為那些不好做動畫 的屬性圖層行為的理想候選跨算。蘋果當(dāng)然意識到了這點爆土,并且當(dāng)設(shè)置了 CALayercontent屬性的時候, CATransition 的確是默認(rèn)的行為诸蚕。但是對于視圖關(guān)聯(lián)的圖層步势,或者是其他隱式動畫的行為氧猬,這個特性依然是被禁用的,但 是對于你自己創(chuàng)建的圖層坏瘩,這意味著對圖層contents圖片做的改動都會自動附上 淡入淡出的動畫盅抚。

我們在第七章使用CATransition作為一個圖層行為來改變圖層的背景色,當(dāng)然backgroundColor屬性可以通過正常的CAPropertyAnimation來實現(xiàn)倔矾,但這不是說不可以用CATransition來實行妄均。

對圖層樹的動畫

CATransition并不作用于指定的圖層屬性,這就是說你可以在即使不能準(zhǔn)確得 知改變了什么的情況下對圖層做動畫哪自,例如丰包,在不知道 UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它壤巷,或者在不知道 UIViewController內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫烫沙。

這些例子和我們之前所討論的情況完全不同,因為它們不僅涉及到圖層的屬性隙笆,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關(guān)系中添加或者移除 圖層锌蓄。

這里用到了一個小詭計,要確保CATransition添加到的圖層在過渡動畫發(fā)生時不會在樹狀結(jié)構(gòu)中被移除,否則CATransition將會和圖層一起被移除. 一般來說,你只需要將動畫添加到被影響圖層的superlayer.

我們展示了如何在 UITabBarController切換標(biāo)簽的時候添加淡入淡出的動畫撑柔。這里我們建立了默認(rèn)的標(biāo)簽應(yīng)用程序模板瘸爽,然后用UITabBarControllerDelegate- tabBarController: deisSelectViewController:方法來應(yīng)用過渡動畫。我們把動畫添加到UITabBarController的視圖圖層上铅忿,于是在標(biāo)簽被替換的時候動畫不會被移除剪决。

@interface AppDelegate ()
@property (nonatomic, strong)UITabBarController *tabBarController;
@end

@implementation AppDelegate 


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    FirstViewController *first = [[FirstViewController alloc]init];
    SecondViewController *second = [[SecondViewController alloc]init];
    self.tabBarController = [[UITabBarController alloc]init];
    self.tabBarController.viewControllers = @[first, second];
    self.tabBarController.delegate = self;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    [self.tabBarController.view.layer addAnimation:transition forKey:nil];
    
}
自定義動畫

我們證實了過渡是一種對那些不太好做平滑動畫屬性的強大工具,但是CATransition的提供的動畫類型太少了檀训。

更奇怪的是蘋果通過UIView +transitionFromView:toView:duration:options:completion:+ transitiononWithView:duration:options:animations:方法提供了Core Animation的過渡特性柑潦。但是這里的可用的過渡選項和CATransitiontype 屬性提供的常量完全不同。UIView 過渡方法中options參數(shù)可以由如下常量指定:

 UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight 
UIViewAnimationOptionTransitionCurlUp 
UIViewAnimationOptionTransitionCurlDown 
UIViewAnimationOptionTransitionCrossDissolve 
UIViewAnimationOptionTransitionFlipFromTop 
UIViewAnimationOptionTransitionFlipFromBottom

除了UIViewAnimationOptionTransitionCrossDissolve 之外峻凫,剩下的值和CATransition 類型完全沒關(guān)系渗鬼。

使用UIKit提供的方法來做過渡動畫

- (void)viewDidLoad {
    [super viewDidLoad];
    self.images = @[
                    [UIImage imageNamed:@"Anchor.png"],
                    [UIImage imageNamed:@"Cone.png"],
                    [UIImage imageNamed:@"Igloo.png"],
                    [UIImage imageNamed:@"Spaceship.png"]
                    ];
}
- (IBAction)changeImage:(id)sender {
    [UIView transitionWithView:self.imageView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
        UIImage *currentImage = self.imageView.image;
        NSUInteger index = [self.images indexOfObject:currentImage];
        index = (index + 1) % [self.images count];
        self.imageView.image = self.images[index];
    } completion:^(BOOL finished) {
        
    }];
    }
使用UIKit提供的方法來做過渡動畫.gif

文檔暗示過在iOS5(帶來了Core Image框架)之后,可以通過 CATransitionfilter 屬性荧琼,用 CIFilter來創(chuàng)建其它的過渡效果譬胎。然是 直到iOS6都做不到這點。試圖對CATransition 使用Core Image的濾鏡完全沒效果(但是在Mac OS中是可行的命锄,也許文檔是想表達(dá)這個意思)堰乔。

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

但這并不意味著在iOS上就不能實現(xiàn)自定義的過渡效果了驶冒。這只是意味著你需要做 一些額外的工作苟翻。就像之前提到的那樣搭伤,過渡動畫做基礎(chǔ)的原則就是對原始的圖層 外觀截圖,然后添加一段動畫袜瞬,平滑過渡到圖層改變之后那個截圖的效果怜俐。如果我 們知道如何對圖層截圖,我們就可以使用屬性動畫來代替 CATransition 或者是 UIKit的過渡方法來實現(xiàn)動畫邓尤。

事實證明拍鲤,對圖層做截圖還是很簡單的。CALayer有一個- renderInContext:方法汞扎,可以通過把它繪制到Core Graphics的上下文中捕獲當(dāng) 前內(nèi)容的圖片季稳,然后在另外的視圖中顯示出來。如果我們把這個截屏視圖置于原始視圖之上澈魄,就可以遮住真實視圖的所有變化景鼠,于是重新創(chuàng)建了一個簡單的過渡效 果。

Demo 我們對當(dāng)前視圖狀態(tài)截圖痹扇,然后在我們改變原始 視圖的背景色的時候?qū)貓D快速轉(zhuǎn)動并且淡出铛漓,為了讓事情更簡單,我們用UIView - animateWithDuration: completion: 方法 來實現(xiàn)鲫构。雖然用 CABasicAnmation可以達(dá)到同樣的效果浓恶,但是那樣的話我們就 需要對圖層的變換和不透明屬性創(chuàng)建單獨的動畫,然后當(dāng)動畫結(jié)束的時候在 CAAnimationDelegate 中把coverView從屏幕中移除结笨。

這里有個警告:-renderInContext:捕獲了圖層的圖片和子圖層包晰,但是不能對子圖層正確地處理變換效果,而且對視頻和OpenGL內(nèi)容也不起作用炕吸。但是用 CATransition伐憾,或者用私有的截屏方式就沒有這個限制了。

在動畫過程中取消動畫

之前提到過赫模,你可以用-addAnimation:forKey:方法中的 key參數(shù)來在添加動 畫之后檢索一個動畫树肃,使用如下方法:

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

但并不支持在動畫運行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性嘴瓤,或者判斷它是否被添加到當(dāng)前圖層中扫外。

為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:

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

或者移除所有動畫:

- (void)removeAllAnimations;

動畫一旦被移除廓脆,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值。一般說來磁玉,動畫 在結(jié)束之后被自動移除停忿,除非設(shè)置 removedOnCompletionNO ,如果你設(shè)置動 畫在結(jié)束之后不被自動移除蚊伞,那么當(dāng)它不需要的時候你要手動移除它;否則它會一 直存在于內(nèi)存中席赂,直到圖層被銷毀吮铭。

我們來擴(kuò)展之前旋轉(zhuǎn)飛船的示例,這里添加一個按鈕來停止或者啟動動畫颅停。這一次 我們用一個非 nil的值作為動畫的鍵谓晌,以便之后可以移除它。 - animationDidStop:finished:方法中的flag參數(shù)表明了動畫是自然結(jié)束還是 被打斷癞揉,我們可以在控制臺打印出來纸肉。如果你用停止按鈕來終止動畫,它會打印NO 喊熟,如果允許它完成柏肪,它會打印 YES

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) CALayer *shipLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
    self.shipLayer.position = CGPointMake(150, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed:@"Igloo.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
}


- (IBAction)startAnimation:(id)sender {
    
    
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
}

- (IBAction)stopAnimation:(id)sender {
    [self.shipLayer removeAnimationForKey:@"rotateAnimation"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"The animation stopped(finished:%@)",flag ? @"YES" : @"NO");
}

總結(jié)

這一章中芥牌,我們涉及了屬性動畫(你可以對單獨的圖層屬性動畫有更加具體的控制)烦味,動畫組(把多個屬性動畫組合成一個獨立單元)以及過度(影響整個圖層,可以用來對圖層的任何內(nèi)容做任何類型的動畫壁拉,包括子圖層的添加和移除)谬俄。

iOS核心動畫高級技巧--目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弃理,隨后出現(xiàn)的幾起案子凤瘦,更是在濱河造成了極大的恐慌,老刑警劉巖案铺,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔬芥,死亡現(xiàn)場離奇詭異,居然都是意外死亡控汉,警方通過查閱死者的電腦和手機笔诵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姑子,“玉大人乎婿,你說我怎么就攤上這事〗钟樱” “怎么了谢翎?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沐旨。 經(jīng)常有香客問我森逮,道長,這世上最難降的妖魔是什么磁携? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任褒侧,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闷供。我一直安慰自己烟央,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布歪脏。 她就那樣靜靜地躺著疑俭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婿失。 梳的紋絲不亂的頭發(fā)上钞艇,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音移怯,去河邊找鬼香璃。 笑死,一個胖子當(dāng)著我的面吹牛舟误,可吹牛的內(nèi)容都是我干的葡秒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嵌溢,長吁一口氣:“原來是場噩夢啊……” “哼眯牧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赖草,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤学少,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秧骑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體版确,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年乎折,在試婚紗的時候發(fā)現(xiàn)自己被綠了绒疗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡骂澄,死狀恐怖吓蘑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坟冲,我是刑警寧澤磨镶,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站健提,受9級特大地震影響琳猫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矩桂,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一沸移、第九天 我趴在偏房一處隱蔽的房頂上張望痪伦。 院中可真熱鬧侄榴,春花似錦雹锣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桦山,卻和暖如春攒射,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恒水。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工会放, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钉凌。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓咧最,卻偏偏與公主長得像,于是被迫代替她去往敵國和親御雕。 傳聞我的和親對象是個殘疾皇子矢沿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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