顯示動畫
屬性動畫
通過- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;
方法滤馍,我們可以為一個圖層添加一個顯示動畫吏够。但是晰甚,當(dāng)這個圖層并不是一個視圖的圖層屬性的實例時福扬,動畫會發(fā)生兩次脯倒,一個是由我們設(shè)置的顯示動畫引起的愿吹,另一個這是因為圖層的隱式動畫金踪。為了避免這種情況浊洞,我們需要在配置顯示動畫的時候指定它的代理,并實現(xiàn)代理方法-animationDidStop:finished:
胡岔,在其中設(shè)置一個新的事務(wù)法希,并禁用圖層行為。
在設(shè)置顯示動畫的過程中靶瘸,還有一個問題就是在動畫完成之后圖層的狀態(tài)又回到了動畫之前的狀態(tài)苫亦。這是因為在默認(rèn)情況下,動畫完成之后將徹底移除奕锌,不會在其超出時間后還修改呈現(xiàn)層的圖層著觉。一旦動畫完成,呈現(xiàn)層的圖層將回到模型層圖層的值惊暴,而我們又沒有修改圖層屬性的值饼丘,因此動畫完成后圖層顯示的還是之前的狀態(tài)。
這里有兩種解決方案辽话,一種是在設(shè)置動畫后更新圖層對應(yīng)屬性的值:
// Add explicit animation to a single layer
CGFloat red = rand() / (double)INT_MAX;
CGFloat green = rand() / (double)INT_MAX;
CGFloat blue = rand() / (double)INT_MAX;
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)[UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
animation.delegate = self;
[self.colorLayer addAnimation:animation forKey:nil];
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
另一種則是設(shè)置動畫的 fillMode 屬性為 kCAFillModeForward
以留在最終狀態(tài)肄鸽,并設(shè)置removedOnCompletion
為 NO 以防止它被自動移除:
// Add explicit animation to a single layer
CGFloat red = rand() / (double)INT_MAX;
CGFloat green = rand() / (double)INT_MAX;
CGFloat blue = rand() / (double)INT_MAX;
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)[UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[self.colorLayer addAnimation:animation forKey:nil];
需要注意的是:如果將已完成的動畫保持在 layer 上時,會造成額外的開銷油啤,因為渲染器會去進行額外的繪畫工作典徘。
還有很重要的一點就是,當(dāng)我們創(chuàng)建好一個動畫并添加給一個layer時就立刻復(fù)制了一份益咬,因此這個動畫是可以重復(fù)添加個多個layer的逮诲。
關(guān)鍵幀動畫
CAKeyframeAnimation
是另一種UIKit沒有暴露出來但功能強大的類。和CABasicAnimation
類似幽告,CAKeyframeAnimation
同樣是CAPropertyAnimation
的一個子類梅鹦,它依然作用于單一的一個屬性,但是和CABasicAnimation
不一樣的是冗锁,它不限制于設(shè)置一個起始和結(jié)束的值齐唆,而是可以根據(jù)一連串隨意的值來做動畫。
示例代碼:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position.x";
animation.values = @[ @0, @10, @-10, @10, @0 ];
animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ];
animation.duration = 0.4;
animation.additive = YES;
[form.layer addAnimation:animation forKey:@"shake"];
在使用CAKeyframeAnimation
做動畫時需要注意的是CAKeyframeAnimation
并不能把當(dāng)前l(fā)ayer的值作為第一幀動畫冻河,這就導(dǎo)致動畫開始時會突然跳到第一幀的值箍邮,再再動畫完成后恢復(fù)到原來的值茉帅,所以為了動畫的平滑效果,我們需要開始和結(jié)束時的關(guān)鍵幀來匹配當(dāng)前屬性的值锭弊。
CAKeyframeAnimation
還有另外一種方式來指定動畫堪澎,就是使用CGPath。path屬性可以用一種直觀的方式廷蓉,使用Core Graphics函數(shù)定義運動序列來繪制動畫全封。
示例代碼:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//add the ship
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];
//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];
}
虛擬屬性
在layer中,有一種屬性其實是不存在的桃犬,如:transform.rotation
,transform.rotation
和transform.rotation
等行楞。因為CATransform3D
并不是一個對象攒暇,而是一個結(jié)構(gòu)體,也沒有符合KVC相關(guān)屬性子房,它們實際上是CALayer用于處理動畫變換的虛擬屬性形用。
它們不能被直接使用。當(dāng)對他們做動畫時证杭,Core Animation自動地根據(jù)通過CAValueFunction
來計算的值來更新transform
屬性田度。
CAValueFunction
用于把我們賦給虛擬的transform.rotation簡單浮點值轉(zhuǎn)換成真正的用于擺放圖層的CATransform3D矩陣值。我們可以通過設(shè)置CAPropertyAnimation
的valueFunction
屬性來改變解愤,這將會覆蓋默認(rèn)的函數(shù)镇饺。
示例代碼:
- (void)viewDidLoad
{
[super viewDidLoad];
//add the ship
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 128, 128);
shipLayer.position = CGPointMake(150, 150);
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 * 2);
[shipLayer addAnimation:animation forKey:nil];
}
動畫組
CABasicAnimation
和CAKeyframeAnimation
僅僅作用于單獨的屬性,而CAAnimationGroup
可以把這些動畫組合在一起送讲。CAAnimationGroup
是另一個繼承于CAAnimation
的子類奸笤,它添加了一個animations
數(shù)組的屬性,用來組合別的動畫哼鬓。
示例代碼:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150)
controlPoint1:CGPointMake(75, 0)
controlPoint2:CGPointMake(225, 300)];
//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(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
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];
}
過渡
屬性動畫只對layer的可動畫屬性起作用监右,如果要改變一個不可動畫屬性(如圖片、文本)异希,或者從層級關(guān)系中添加或移除圖層健盒,屬性動畫將不再起作用,這時称簿,我們可以使用過渡來實現(xiàn)動畫效果扣癣。
為了創(chuàng)建一個過渡動畫,我們將使用CATransition予跌,同樣是另一個CAAnimation的子類搏色,和別的子類不同,CATransition有一個type和subtype來標(biāo)識變換效果券册。type屬性是一個NSString類型频轿,用來設(shè)置過渡動畫效果垂涯,可以被設(shè)置成如下類型:
- kCATransitionFade
- kCATransitionMoveIn
- kCATransitionPush
- kCATransitionReveal
通過subtype我們可以控制動畫的方向,它提供了如下四種類型:
- kCATransitionFromRight
- kCATransitionFromLeft
- kCATransitionFromTop
- kCATransitionFromBottom
示例代碼:
- (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 + 1) % [self.images count];
self.imageView.image = self.images[index];
}
隱式過渡
對于視圖關(guān)聯(lián)的圖層航邢,或者是其他隱式動畫的行為耕赘,過渡的特性依然是被禁用的,但是對于我們自己創(chuàng)建的圖層膳殷,這意味著對圖層contents圖片做的改動都會自動附上淡入淡出的動畫操骡。
對圖層樹的動畫
CATransition
并不作用于指定的圖層屬性,這就是說我們可以在即使不能準(zhǔn)確得知改變了什么的情況下對圖層做動畫赚窃,例如册招,在不知道UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它勒极,或者在不知道UIViewController內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫是掰。
在這里,我們只要將過渡動畫添加到被影響圖層的superlayer
辱匿,就實現(xiàn)了對整個圖層樹添加過渡動畫键痛。
對UITabBarController
添加過渡動畫,示例代碼:
#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];
}
@end
在動畫過程中取消動畫
移除指定動畫:
- (void)removeAnimationForKey:(NSString *)key;
移除所有動畫:
- (void)removeAllAnimations;
圖層時間
CAMediaTiming協(xié)議
CAMediaTiming協(xié)議定義了在一段動畫內(nèi)用來控制逝去時間的屬性的集合匾七,CALayer和CAAnimation都實現(xiàn)了這個協(xié)議絮短,所以時間可以被任意基于一個圖層或者一段動畫的類控制。
持續(xù)和重復(fù)
duration
:設(shè)置動畫迭代一次的時間
repeatCount
:設(shè)置動畫迭代次數(shù)
動畫時長 = duration
* repeatCount
repeatDuration
:設(shè)置動畫重復(fù)時長(當(dāng)時此屬性值與duration的值一致時動畫不重復(fù))
autoreverses
:完成一次動畫迭代后昨忆,自動后退到圖層原始狀態(tài)
相對時間
beginTime
:設(shè)置動畫開始延遲時間
speed
:設(shè)置動畫播放倍數(shù)丁频,默認(rèn)為1.0
timeOffset
:設(shè)置動畫播放位置(0~1)
fillMode
fillMode
用來設(shè)置動畫開始前和結(jié)束后的這段時間動畫屬性的值,它是一個NSString類型扔嵌,可以接受如下四種常量:
- kCAFillModeForwards
- kCAFillModeBackwards
- kCAFillModeBoth
- kCAFillModeRemoved
注意限府,如果要圖層保持動畫結(jié)束時的狀態(tài),除了要設(shè)置fillMode
痢缎,還需要將removeOnCompletion
設(shè)置為NO
胁勺。另外,將已完成的動畫保持在layer上独旷,會造成額外的開銷署穗,因為渲染器需要進行額外的繪制工作。所以嵌洼,最好是在為layer
添加動畫的時候設(shè)置一個key案疲,這樣就可以在不需要動畫的時候?qū)⑺鼜?code>layer上移除。
層級關(guān)系時間
在圖層樹中每個圖層都有一個特定的坐標(biāo)系統(tǒng)來定義該圖層和它的父圖層之間的關(guān)系麻养,動畫時間同樣也是如此褐啡。每個圖層和動畫都有一套基于它父視圖的層級關(guān)系概念上的時間。改變一個layer的時間將會影響該圖層和它的子圖層的動畫鳖昌,但不會對它的父圖層產(chǎn)生影響备畦。這點同樣適用于由CAAnimationGroup
組成的動畫低飒。
調(diào)整CALayer
或CAAnimationGroup
的duration
,repeatCount
和repeatDuration
屬性不會對子圖層的動畫產(chǎn)生影響懂盐,而調(diào)整beginTime
, timeOffset
和speed
屬相將會對子視圖的動畫也產(chǎn)生影響褥赊。
全局時間和局部時間
在Core Animation當(dāng)中,有著全局時間和局部時間的概念莉恼。全局時間以設(shè)備時間為標(biāo)準(zhǔn)拌喉,通過如下方法可以獲得:
CFTimeInterval time = CACurrentMediaTime();
局部時間是每個layer的時間體系,當(dāng)beginTime
,timeOffset
或speed
屬性發(fā)生改變時俐银,局部時間可能會和全局時間不一致尿背。通過以下方法可以在轉(zhuǎn)換局部時間:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
暫停,倒放和快進
當(dāng)將一個動畫的speed
屬性設(shè)為0捶惜,動畫將暫停残家,但是這不會對正在動畫中的layer產(chǎn)生影響,因為當(dāng)我們將一個動畫添加到一個layer上時售躁,是將動畫copy然后賦值給layer。我們可以通過-animationForKey:
獲取到正在動畫的layer真正的動畫茴晋,但是陪捷,即使獲取到了正確的動畫,我們依然不能通過直接修改動畫的speed
屬性來讓動畫暫停诺擅。
當(dāng)然市袖,我們也可以在動畫被移除錢獲取presentation layer的屬性值然后賦值給model layer。但是這樣做有一個缺點就是烁涌,在之后想要再繼續(xù)動畫將會很復(fù)雜苍碟。
暫停動畫正確地姿勢是利用層級關(guān)系時間。也就是調(diào)整動畫所屬layer的speed
屬性撮执。
如果要調(diào)整整個app的動畫速度微峰,我們只需要如此這般:
self.window.layer.speed = 100;
手動動畫
通過layer的timeOffset
屬性和UIPanGestureRecognizer
手勢,我們可以很容易地實現(xiàn)一個手動控制的動畫抒钱。
緩沖
動畫速度
CAMediaTimingFunction
通過設(shè)置CAAnimation
的timingFunction
屬性蜓肆,我們可以改變動畫的緩沖函數(shù):
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
如果要改變隱式動畫的緩沖函數(shù),我們可以使用CATransaction
的+setAnimationTimingFunction:
方法:
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
創(chuàng)建CAMediaTimingFunction
最簡單的方法是調(diào)用+timingFunctionWithName:
的構(gòu)造方法谋币。這里傳入如下幾個常量之一:
- kCAMediaTimingFunctionLinear
- kCAMediaTimingFunctionEaseIn
- kCAMediaTimingFunctionEaseOut
- kCAMediaTimingFunctionEaseInEaseOut
- kCAMediaTimingFunctionDefault
UIView的動畫緩沖
UIKit的動畫也同樣支持這些緩沖方法的使用仗扬,盡管語法和常量有些不同,為了改變UIView動畫的緩沖選項蕾额,給options參數(shù)添加如下常量之一:
- UIViewAnimationOptionCurveEaseInOut
- UIViewAnimationOptionCurveEaseIn
- UIViewAnimationOptionCurveEaseOut
- UIViewAnimationOptionCurveLinear
示例代碼:
[UIView animateWithDuration:5
delay:1
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view.layer.backgroundColor = [UIColor redColor].CGColor;
}
completion:NULL];
緩沖和關(guān)鍵幀動畫
CAKeyframeAnimation
有一個NSArray類型的timingFunctions
屬性早芭,我們可以用它來對每次動畫的步驟指定不同的計時函數(shù)。但是指定函數(shù)的個數(shù)一定要等于keyframes數(shù)組的元素個數(shù)減一诅蝶,因為它是描述每一幀之間動畫速度的函數(shù)退个。