文章分享至個人我的技術博客: https://cainluo.github.io/14807833712288.htmlhtml
在上一篇文章《Core Animation》CALayer的Specialized Layers中, 咱們瞭解了CALayer的許多子類特性, 能夠爲咱們在遇到一些特殊的開發需求中提供必定的幫助, 既然咱們此次學的的Core Animation, 那怎麼會和動畫不掛鉤呢? 此次讓咱們來初體驗一下.git
** 最後:** ** 若是你有更好的建議或者對這篇文章有不滿的地方, 請聯繫我, 我會參考大家的意見再進行修改, 聯繫我時, 請備註**`Core Animation`**若是以爲好的話, 但願你們也能夠打賞一下~嘻嘻~祝你們學習愉快~謝謝~**
Implicit Animations也稱爲隱式動畫, 啥? 什麼叫作隱式動畫? 百度去吧~~哈哈哈(後面會講解的), 這些問題就不在這裏作解釋了, 仍是進入主題才比較重要.github
其實在Core Animation中, 動畫效果並不須要咱們去手動打開, 由於系統默認就是Open狀態, 相反過來, 若是咱們不須要動畫的話, 咱們須要手動的去關閉. 若是咱們用一個CALayer的一個動畫屬性, 而且嘗試去改變它, 這個效果並不會立刻就顯示出來, 由於它要從一個默認值平滑的過分到一個新的值, 而這些全部的內部操做咱們都不須要去理會, 由於系統默認就是這麼作的. 咱們能夠先來看個Demo:vim
- (void)transactionsColor {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,
100,
self.view.frame.size.width,
self.view.frame.size.width)];
view.backgroundColor = [UIColor grayColor];
[self.view addSubview:view];
UIButton *button = [[UIButton alloc] init];
button.center = CGPointMake(self.view.frame.size.width / 2, 50);
button.bounds = CGRectMake(0, 0, 100, 50);
button.backgroundColor = [UIColor blueColor];
[button setTitle:@"改變顏色"
forState:UIControlStateNormal];
[button addTarget:self
action:@selector(changeLayerColor)
forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
self.colorLayer = [CALayer layer];
self.colorLayer.position = CGPointMake(view.frame.size.width / 2, view.frame.size.height / 2);
self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
[view.layer addSublayer:self.colorLayer];
}
- (void)changeLayerColor {
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
}
複製代碼
看完這個Demo其實就已經知道神馬叫作隱式動畫了, 所謂的隱式動畫就是咱們沒有給它指定任何的動畫類型, 僅僅只是改變某個屬性, 固然Core Animation也是支持顯示動畫, 否則咱們就沒那麼多的興趣來學習Core Animation了~ 那麼當咱們去改變一個屬性的時候, Core Animation是如何去判斷動畫類型還有動畫的持續時間呢? 這個問題其實也很簡單, 動畫的執行時間取決於Transactions的設置, 而動畫類型是取決於CALayer的行爲. 其實Transactions其實是Core Animation用來包含一堆屬性動畫集合的機制, 任何用指定Transactions去改變能夠作動畫效果的圖層屬性都不會立刻發生變化, 而是須要Transactions在提交的一瞬間, 纔會開始用一個動畫效果過渡到新設置的值. 而Transactions是須要經過CATransaction這個類來進行管理的, 奇怪的是, CATransaction這個類並非管理一個簡單的Transactions, 而是管理了一堆咱們不能訪問的Transactions, 因爲CATransaction並無屬性和實例化方法, 也不能用**+ (instancetype)alloc;和- (instancetype)init;方法來建立它, 只有它所提供的+ (void)begin;和+ (void)commit;來控制. 雖然咱們再上面的Demo裏沒有設置動畫時間, 但Core Animation會在每個run looop週期中自動開始一次新的Transactions**, 即便咱們不手動的去調用**[CATransaction begin];, 但在每一次run loop**的循環中, 被修改的屬性都會集中起來, 而後統一作一次0.25秒的動畫, 這個是系統默認的. 說了那麼多, 咱們實際上來改改修改顏色的那個代碼塊, 讓它有一個顯示動畫的效果:微信
- (void)changeLayerColorAgain {
[CATransaction begin];
[CATransaction setAnimationDuration:2.0f];
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
[CATransaction commit];
}
複製代碼
看起來的效果讓人以爲是真的有動畫效果了, 若是你們在以前就已經用過UIView來作過動畫的話, 那麼你們應該對這個動畫模式不會感受到陌生, 由於UIView就有兩個相似的方法, + (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;和+ (void)commitAnimations;, 其實這兩個這兩個方法也是由於在內部設置了CATransaction的緣由. 在iOS 4的時候, 蘋果就已經對UIView添加了一種基於Block的動畫方法, + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0);, 用起來更加的方便, 但其實是作一樣的事情, 但使用這種方法就能夠避免**+ (void)begin;和+ (void)commit;**匹配的問題形成一些蛋疼的事情.dom
這裏咱們就使用一下基於UIView的Block動畫方法, 咱們能夠在動畫結束以後再對這個圖層進行一些操做, 固然這裏仍是基於上面的Demo來作演示:ide
- (void)changeLayerColorWithCompletion {
[CATransaction begin];
[CATransaction setAnimationDuration:2.0f];
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_4);
self.colorLayer.affineTransform = transform;
}];
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
[CATransaction commit];
}
複製代碼
開始的時候咱們就用一個Demo來進行演示:函數
- (void)addLayerView {
self.layerView = [[UIView alloc] init];
self.layerView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
self.layerView.bounds = CGRectMake(0, 0, 150, 150);
self.layerView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.layerView];
UIButton *button = [[UIButton alloc] init];
button.center = CGPointMake(self.view.frame.size.width / 2, 200);
button.bounds = CGRectMake(0, 0, 100, 50);
button.backgroundColor = [UIColor blueColor];
[button setTitle:@"改變顏色"
forState:UIControlStateNormal];
[button addTarget:self
action:@selector(changeLayerViewColor)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)changeLayerViewColor {
[CATransaction begin];
[CATransaction setAnimationDuration:2.0f];
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.layerView.layer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
[CATransaction commit];
}
複製代碼
看完這個Demo, 有不少人確定會有疑問, 爲啥沒有了以前的那個平滑過渡效果呢? 好像是被幹掉了, 這是啥回事? 其實咱們能夠仔細想想, 若是UIView裏的屬性都有動畫特性的話, 那咱們去修改這些屬性時, 確定會注意到的, 可爲啥UIKit要把這個隱式動畫給禁止呢? 咱們都知道Core Animation一般會對CALayer全部的可作動畫的屬性都賦予了動畫特性, 但在UIView中就不同了, 它會默認把所關聯在一塊兒的CALayer的這個特性給關閉掉, 這裏就要了解一下隱式動畫是如何實現的. 當咱們改變CALayer屬性時, CALayer自動應用的動畫, 咱們能夠成爲CALayer的行爲, 每當CALayer的屬性被修改的時候, 它會去調用**- (nullable id)actionForKey:(NSString *)event;**這個方法去傳遞屬性的名稱, 而後就會去執行以下幾步:oop
首先CALayer會去檢測它是否有Delegate, 而且看看這個Delegate有沒有實現CALayerDelegate協議裏的**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;**方法, 若是有, 就直接調用並返回結果.佈局
若是CALayer沒有Delegate的話, 或者Delegate沒有實現**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;方法, 那麼圖層就會接着去檢查包含屬性名稱對應的CALayer行爲所映射的Actions**字典.
若是Actions字典沒有包含對應的屬性, 那麼圖層接着會在它的style字典裏接着搜索屬性名.
最後, 在style裏也找不到對應的行爲, 那麼圖層就會直接調用**+ (nullable id)defaultActionForKey:(NSString *)event;實現系統所提供的每一個屬性的默認行爲. 若是一輪完整的搜索結束以後, - (nullable id)actionForKey:(NSString *)event;返回爲空的話, 那麼確定不會有動畫效果, 若是返回CAAction協議對應的對象, CALayer會拿這個結果去對比先前和當前的值, 而且作一個動畫效果. 知道這個原理以後, 咱們就知道UIKit**是腫麼把隱式動畫給禁止掉了:
每個UIView對它所關聯的圖層都是充當一個Delegate對象, 而且提供了**- (nullable id)actionForKey:(NSString *)event;**的實現方法.
當不在一個動畫塊的實現中, 那麼UIView就會對全部CALayer的行爲返回nil, 若是在動畫的Block範圍以內, UIView就會返回一個非空的值. 這裏咱們簡單的Log一下結果:
- (void)checkViewAction {
UIView *layerView = [[UIView alloc] init];
layerView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
layerView.bounds = CGRectMake(0, 0, 150, 150);
layerView.backgroundColor = [UIColor redColor];
[self.view addSubview:layerView];
NSLog(@"Before: %@", [layerView actionForLayer:layerView.layer
forKey:@"backgroundColor"]);
[UIView beginAnimations:nil
context:nil];
NSLog(@"After: %@", [layerView actionForLayer:layerView.layer
forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
複製代碼
2016-12-04 12:45:28.178 7.ImplicitAnimations[57079:2126402] Before: <null>
2016-12-04 12:45:28.179 7.ImplicitAnimations[57079:2126402] After: <CABasicAnimation: 0x6000000327c0>
複製代碼
這樣子咱們就能夠知道, 當屬性在Block以外發生改變, UIView會直接經過返回nil來禁用隱式動畫, 但若是在動畫塊的範圍以內, 就會根據動畫的具體類型來返回相應的屬性, 這個後續會講到. 其實除了經過返回nil並非惟一禁止隱式動畫的方法, 咱們也能夠經過CATransacition的**+ (void)setDisableActions:(BOOL)flag;方法, 經過flag來對全部屬性打開或者關閉隱式動畫, 哪怕你是在[CATransaction begin];以後來添加, 也是同樣能夠關閉的. 這裏還有一個Demo**, 使用CATransaction來實現的一個叫作推動過渡動畫, 其實說白也就是一個Push動畫:
- (void)pushAnimation {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,
100,
self.view.frame.size.width,
self.view.frame.size.width)];
view.backgroundColor = [UIColor grayColor];
[self.view addSubview:view];
UIButton *button = [[UIButton alloc] init];
button.center = CGPointMake(self.view.frame.size.width / 2, 50);
button.bounds = CGRectMake(0, 0, 100, 50);
button.backgroundColor = [UIColor blueColor];
[button setTitle:@"改變顏色"
forState:UIControlStateNormal];
[button addTarget:self
action:@selector(pushChangeColor)
forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
self.colorLayer = [CALayer layer];
self.colorLayer.position = CGPointMake(view.frame.size.width / 2, view.frame.size.height / 2);
self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.colorLayer.actions = @{@"backgroundColor": transition};
[view.layer addSublayer:self.colorLayer];
}
- (void)pushChangeColor {
[CATransaction begin];
[CATransaction setAnimationDuration:2.0f];
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
[CATransaction commit];
}
複製代碼
其實仔細想一想, CALayer的屬性行爲並不太正常, 爲什麼這麼說呢, 由於當咱們去改變一個圖層的屬性時, 咱們會發現, 這個值的確是當即發生了改變, 但在屏幕上並無立刻生效, 爲什麼呢? 由於咱們在設置屬性的時候, 並無直接去調整圖層的顯示外觀, 僅僅只是定義了圖層動畫結束以後即將要發生改變的外觀. Core Animation在這裏充當了一個控制器的角色, 而且根據Layer Actions和Transactions來更新視圖在屏幕上顯示的狀態. 在於用戶交互的界面中, CALayer的行爲更像是保存着視圖如何去顯示和動畫的執行數據模型. 在iOS中, 屏幕會以每秒鐘重繪60次, 若是動畫市場比60分之一秒還要長, 那麼在這段時間裏, Core Animation就會對屏幕上的圖層進行從新的組合, 這就意味着CALayer除了咱們給予的值以外, 還必需要知道當前顯示在屏幕上的屬性值的記錄. 而每一個圖層屬性的顯示值都會被存儲在一個叫作呈現圖層的獨立圖層當中, 咱們能夠經過**- (nullable instancetype)presentationLayer;方法來訪問, 而這個所謂的呈現圖層**, 實際上就是模型圖層的複製, 但它的好處是它的屬性值表明了在任何指定時間當前所顯示的外觀效果, 通俗點來說, 就是咱們能夠經過獲取呈現圖層的值來獲取當前屏幕上真正顯示出來的值. 這裏須要注意的一點就是, 若是在呈現圖層僅僅當CALayer首次被提交的時候建立, 那麼去調用**- (nullable instancetype)presentationLayer;方法就會返回nil**. 這裏咱們或許還會注意到另外一個方法**- (instancetype)modelLayer;, 若是咱們在呈現圖層上調用這個方法, 那麼就會返回一個它正在呈現因此來的CALayer**, 而一般在一個圖層上調用這個方法, 就會返回self. 在大多數開發的場景下, 咱們都不須要直接訪問呈現圖層, 咱們能夠經過和模型圖層的交互, 來讓Core Animation更新而且顯示, 但在如下兩種場景下呈現圖層就很是有用了, 一個是在同步動畫裏, 一個是在處理用戶交互的時候:
- 若是咱們在實現一個基於定時器的動畫, 而不只僅是基於Transactions的動畫, 這個時候咱們就要準確的知道在某一時刻圖層顯示在什麼位置, 這就會對正確的佈局起很是大的做用了.
- 若是咱們想讓作動畫的圖層對於用戶有交互, 咱們可使用**- (nullable CALayer *)hitTest:(CGPoint)p;方法來判斷指定的圖層是否被點擊了, 這個時候就會顯示更加的友好, 由於呈現圖層表明了用戶當前看到的圖層位置, 而不是當動畫效果結束以後的位置. 說了那麼多, 仍是直接上Demo**比較直接:
- (void)presentationVersusModel {
self.colorLayer = [CALayer layer];
self.colorLayer.position = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
self.colorLayer.bounds = CGRectMake(0, 0, 150, 150);
self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self.view];
if ([self.colorLayer.presentationLayer hitTest:point]) {
CGFloat redColor = arc4random() / (CGFloat)INT_MAX;
CGFloat greenColor = arc4random() / (CGFloat)INT_MAX;
CGFloat blueColor = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:redColor
green:greenColor
blue:blueColor
alpha:1.0f].CGColor;
} else {
[CATransaction begin];
[CATransaction setAnimationDuration:4.0f];
self.colorLayer.position = point;
[CATransaction commit];
}
}
複製代碼
總結一下:
- Core Animation默認是打開動畫效果的, 而且默認的動畫效果是平滑過渡滴.
- 咱們知道了隱式動畫的實現方式.
- UIView關聯的圖層默認都禁用了隱式動畫, 對這種圖層作動畫的惟一辦法就是使用UIView的動畫函數, 或者是繼承與UIView而且重寫**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;**方法, 最直接的方法就是直接建立一個顯示動畫.
- 對於一個單獨存在的圖層來說, 咱們能夠經過實現圖層的**- (nullable id)actionForLayer:(CALayer *)layer forKey:(NSString *)event;方法, 或者是提供一個Actions**的字典來控制隱式動畫.
- 除此以外, 咱們來了解了呈現圖層和模型圖層, 知道了這兩個傢伙的一些皮毛. 好了, 此次就到這裏了, 謝謝你們~
項目地址: https://github.com/CainRun/CoreAnimation