最近看了點(diǎn)動(dòng)畫(huà)方面的知識(shí),做個(gè)筆記記錄一下。
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *contentView;
@property (nonatomic,strong) CALayer *colorlayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.colorlayer = [CALayer layer];
self.colorlayer.frame = CGRectMake(0, 0, 100, 100);
self.colorlayer.backgroundColor = [UIColor redColor].CGColor;
[self.contentView.layer addSublayer:self.colorlayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGFloat red = arc4random_uniform(256) / 255.0;
CGFloat gre = arc4random_uniform(256) / 255.0;
CGFloat blu = arc4random_uniform(256) / 255.0;
self.colorlayer.backgroundColor = [UIColor colorWithRed:red green:gre blue:blu alpha:1.0].CGColor;
}
這就是隱式動(dòng)畫(huà),僅僅改變了Layer的backgroundColor屬性,運(yùn)行卻有動(dòng)畫(huà)效果。
實(shí)際上動(dòng)畫(huà)執(zhí)行的時(shí)間取決于當(dāng)前事務(wù)的設(shè)置,動(dòng)畫(huà)類(lèi)型取決于圖層行為。
事務(wù)是通過(guò)CATransaction類(lèi)來(lái)做管理,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à)。
所以事務(wù)是通過(guò)CATransaction類(lèi)隱式得設(shè)置了動(dòng)畫(huà)執(zhí)行時(shí)間,我們也可以通過(guò)setAnimationDuration設(shè)置動(dòng)畫(huà)時(shí)間。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 如果我們要自己通過(guò)setAnimationDuration設(shè)置動(dòng)畫(huà)執(zhí)行時(shí)間,必須要先起一個(gè)新的事務(wù),因?yàn)樾薷漠?dāng)前事務(wù)的時(shí)間可能會(huì)導(dǎo)致同一時(shí)刻別的動(dòng)畫(huà),所以最好是在調(diào)整動(dòng)畫(huà)之前壓入一個(gè)新的事務(wù)。
[CATransaction begin];
// 默認(rèn)為0.25秒
[CATransaction setAnimationDuration:0.25];
CGFloat red = arc4random_uniform(256) / 255.0;
CGFloat gre = arc4random_uniform(256) / 255.0;
CGFloat blu = arc4random_uniform(256) / 255.0;
self.colorlayer.backgroundColor = [UIColor colorWithRed:red green:gre blue:blu alpha:1.0].CGColor;
[CATransaction commit];
}
UIView有兩個(gè)方法,+beginAnimations:context:和+commitAnimations、+animateWithDuration:animations:,和CATransaction的+begin和+commit方法類(lèi)似。實(shí)際上在+beginAnimations:context:和+commitAnimations之間所有視圖或者圖層屬性的改變而做的動(dòng)畫(huà)都是由于設(shè)置了CATransaction的原因。
UIView的+animateWithDuration:animations:提供了一個(gè)block允許在動(dòng)畫(huà)結(jié)束后做一些操作,CATransaction也提供了一個(gè)+setCompletionBlock:方法。
關(guān)于隱式動(dòng)畫(huà),還有最重要的一點(diǎn)是:rootLayer不執(zhí)行隱式動(dòng)畫(huà)。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.contentView.layer.backgroundColor = [UIColor redColor].CGColor;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGFloat red = arc4random_uniform(256) / 255.0;
CGFloat gre = arc4random_uniform(256) / 255.0;
CGFloat blu = arc4random_uniform(256) / 255.0;
self.contentView.layer.backgroundColor = [UIColor colorWithRed:red green:gre blue:blu alpha:1.0].CGColor;
}
點(diǎn)擊屏幕,圖層顏色是瞬間切換的,沒(méi)有了動(dòng)畫(huà)效果。我們知道動(dòng)畫(huà)類(lèi)型取決于圖層行為,圖層行為是
我們把改變屬性時(shí)CALayer自動(dòng)應(yīng)用的動(dòng)畫(huà)稱作行為,當(dāng)CALayer的屬性被修改時(shí)候,它會(huì)調(diào)用-actionForKey:方法,傳遞屬性的名稱。剩下的操作都在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方法,圖層接著檢查包含屬性名稱對(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è)非空值。
我們可以測(cè)試一下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.contentView.layer.backgroundColor = [UIColor redColor].CGColor;
NSLog(@"1 --- %@",[self.contentView actionForLayer:self.contentView.layer forKey:@"backgroundColor"]);
[UIView beginAnimations:nil context:nil];
NSLog(@"2 --- %@",[self.contentView actionForLayer:self.contentView.layer forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
2017-04-11 11:05:22.758 隱式動(dòng)畫(huà)[3583:3064040] 1 --- <null>
2017-04-11 11:05:22.759 隱式動(dòng)畫(huà)[3583:3064040] 2 --- <CABasicAnimation: 0x61000003da00>```
所以-actionForKey:返回nil,這種情況下就不執(zhí)行動(dòng)畫(huà)。我們也可以通過(guò)[CATransaction setDisableActions:YES];阻止動(dòng)畫(huà)執(zhí)行。
行為通常是一個(gè)被Core Animation隱式調(diào)用的顯式動(dòng)畫(huà)對(duì)象。
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.colorLayer.actions = @{@"backgroundColor": transition};
[self.contentView.layer addSublayer:self.colorlayer];
需要注意的是動(dòng)畫(huà)效果的代碼要在layer添加之前。