iOS Core Animation Advanced Techniques學(xué)習(xí)筆記(5)

顯示動畫

屬性動畫

通過- (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.rotationtransform.rotationtransform.rotation等行楞。因為CATransform3D并不是一個對象攒暇,而是一個結(jié)構(gòu)體,也沒有符合KVC相關(guān)屬性子房,它們實際上是CALayer用于處理動畫變換的虛擬屬性形用。

它們不能被直接使用。當(dāng)對他們做動畫時证杭,Core Animation自動地根據(jù)通過CAValueFunction來計算的值來更新transform屬性田度。

CAValueFunction用于把我們賦給虛擬的transform.rotation簡單浮點值轉(zhuǎn)換成真正的用于擺放圖層的CATransform3D矩陣值。我們可以通過設(shè)置CAPropertyAnimationvalueFunction屬性來改變解愤,這將會覆蓋默認(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];
}

動畫組

CABasicAnimationCAKeyframeAnimation僅僅作用于單獨的屬性,而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)整CALayerCAAnimationGroupdurationrepeatCountrepeatDuration屬性不會對子圖層的動畫產(chǎn)生影響懂盐,而調(diào)整beginTime, timeOffsetspeed 屬相將會對子視圖的動畫也產(chǎn)生影響褥赊。

全局時間和局部時間

在Core Animation當(dāng)中,有著全局時間和局部時間的概念莉恼。全局時間以設(shè)備時間為標(biāo)準(zhǔn)拌喉,通過如下方法可以獲得:

CFTimeInterval time = CACurrentMediaTime();

局部時間是每個layer的時間體系,當(dāng)beginTime,timeOffsetspeed屬性發(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è)置CAAnimationtimingFunction屬性蜓肆,我們可以改變動畫的緩沖函數(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ù)退个。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末募壕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帜乞,更是在濱河造成了極大的恐慌司抱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黎烈,死亡現(xiàn)場離奇詭異习柠,居然都是意外死亡,警方通過查閱死者的電腦和手機照棋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門资溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烈炭,你說我怎么就攤上這事溶锭。” “怎么了符隙?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵趴捅,是天一觀的道長。 經(jīng)常有香客問我霹疫,道長拱绑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任丽蝎,我火速辦了婚禮猎拨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屠阻。我一直安慰自己红省,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布国觉。 她就那樣靜靜地躺著吧恃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛉加。 梳的紋絲不亂的頭發(fā)上蚜枢,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音针饥,去河邊找鬼厂抽。 笑死,一個胖子當(dāng)著我的面吹牛丁眼,可吹牛的內(nèi)容都是我干的筷凤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼藐守!你這毒婦竟也來了挪丢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤卢厂,失蹤者是張志新(化名)和其女友劉穎乾蓬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慎恒,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡任内,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了融柬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片死嗦。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粒氧,靈堂內(nèi)的尸體忽然破棺而出越除,到底是詐尸還是另有隱情,我是刑警寧澤外盯,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布摘盆,位于F島的核電站,受9級特大地震影響饱苟,放射性物質(zhì)發(fā)生泄漏骡澈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一掷空、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧囤锉,春花似錦坦弟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至驱入,卻和暖如春赤炒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亏较。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工莺褒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雪情。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓遵岩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尘执,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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