在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ View-Layer 協(xié)作
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

View-Layer 協(xié)作

在 iOS 中,所有的 view 都是由一個(gè)底層的 layer 來驅(qū)動(dòng)的。view 和它的 layer 之間有著緊密的聯(lián)系,view 其實(shí)直接從 layer 對(duì)象中獲取了絕大多數(shù)它所需要的數(shù)據(jù)。在 iOS 中也有一些單獨(dú)的 layer,比如 AVCaptureVideoPreviewLayerCAShapeLayer,它們不需要附加到 view 上就可以在屏幕上顯示內(nèi)容。兩種情況下其實(shí)都是 layer 在起決定作用。當(dāng)然了,附加到 view 上的 layer 和單獨(dú)的 layer 在行為上還是稍有不同的。

基本上你改變一個(gè)單獨(dú)的 layer 的任何屬性的時(shí)候,都會(huì)觸發(fā)一個(gè)從舊的值過渡到新值的簡(jiǎn)單動(dòng)畫(這就是所謂的可動(dòng)畫 animatable)。然而,如果你改變的是 view 中 layer 的同一個(gè)屬性,它只會(huì)從這一幀直接跳變到下一幀。盡管兩種情況中都有 layer,但是當(dāng) layer 附加在 view 上時(shí),它的默認(rèn)的隱式動(dòng)畫的 layer 行為就不起作用了。

animatable;幾乎所有的層的屬性都是隱性可動(dòng)畫的。你可以在文檔中看到它們的簡(jiǎn)介是以 'animatable' 結(jié)尾的。這不僅包括了比如位置,尺寸,顏色或者透明度這樣的絕大多數(shù)的數(shù)值屬性,甚至也囊括了像 isHidden 和 doubleSided 這樣的布爾值。 像 paths 這樣的屬性也是 animatable 的,但是它不支持隱式動(dòng)畫。

在 Core Animation 編程指南的 “How to Animate Layer-Backed Views” 中,對(duì)_為什么_會(huì)這樣做出了一個(gè)解釋:

UIView 默認(rèn)情況下禁止了 layer 動(dòng)畫,但是在 animation block 中又重新啟用了它們

這正是我們所看到的行為;當(dāng)一個(gè)屬性在動(dòng)畫 block 之外被改變時(shí),沒有動(dòng)畫,但是當(dāng)屬性在動(dòng)畫 block 內(nèi)被改變時(shí),就帶上了動(dòng)畫。對(duì)于這是_如何_發(fā)生的這一問題的答案十分簡(jiǎn)單和優(yōu)雅,它優(yōu)美地闡明和揭示了 view 和 layer 之間是如何協(xié)同工作和被精心設(shè)計(jì)的。

無論何時(shí)一個(gè)可動(dòng)畫的 layer 屬性改變時(shí),layer 都會(huì)尋找并運(yùn)行合適的 'action' 來實(shí)行這個(gè)改變。在 Core Animation 的專業(yè)術(shù)語(yǔ)中就把這樣的動(dòng)畫統(tǒng)稱為動(dòng)作 (action,或者 CAAction)。

CAAction:技術(shù)上來說,這是一個(gè)接口,并可以用來做各種事情。但是實(shí)際中,某種程度上你可以只把它理解為用來處理動(dòng)畫。

layer 將像文檔中所寫的的那樣去尋找動(dòng)作,整個(gè)過程分為五個(gè)步驟。第一步中的在 view 和 layer 中交互的部分是最有意思的:

layer 通過向它的 delegate 發(fā)送 actionForLayer:forKey: 消息來詢問提供一個(gè)對(duì)應(yīng)屬性變化的 action。delegate 可以通過返回以下三者之一來進(jìn)行響應(yīng):

  1. 它可以返回一個(gè)動(dòng)作對(duì)象,這種情況下 layer 將使用這個(gè)動(dòng)作。
  2. 它可以返回一個(gè) nil, 這樣 layer 就會(huì)到其他地方繼續(xù)尋找。
  3. 它可以返回一個(gè) NSNull 對(duì)象,告訴 layer 這里不需要執(zhí)行一個(gè)動(dòng)作,搜索也會(huì)就此停止。

而讓這一切變得有趣的是,當(dāng) layer 在背后支持一個(gè) view 的時(shí)候,view 就是它的 delegate;

在 iOS 中,如果 layer 與一個(gè) UIView 對(duì)象關(guān)聯(lián)時(shí),這個(gè)屬性必須被設(shè)置為持有這個(gè) layer 的那個(gè) view。

理解這些之后,前一分鐘解釋起來還復(fù)雜無比的現(xiàn)象瞬間就易如反掌了:屬性改變時(shí) layer 會(huì)向 view 請(qǐng)求一個(gè)動(dòng)作,而一般情況下 view 將返回一個(gè) NSNull,只有當(dāng)屬性改變發(fā)生在動(dòng)畫 block 中時(shí),view 才會(huì)返回實(shí)際的動(dòng)作。哈,但是請(qǐng)別輕信我的這些話,你可以非常容易地驗(yàn)證到底是不是這樣。只要對(duì)一個(gè)一般來說可以動(dòng)畫的 layer 屬性向 view 詢問動(dòng)作就可以了,比如對(duì)于 'position':

NSLog(@"outside animation block: %@",
      [myView actionForLayer:myView.layer forKey:@"position"]);

[UIView animateWithDuration:0.3 animations:^{
    NSLog(@"inside animation block: %@",
          [myView actionForLayer:myView.layer forKey:@"position"]);
}];

運(yùn)行上面的代碼,可以看到在 block 外 view 返回的是 NSNull 對(duì)象,而在 block 中時(shí)返回的是一個(gè) CABasicAnimation。很優(yōu)雅,對(duì)吧?值得注意的是打印出的 NSNull 是帶著一對(duì)尖括號(hào)的 ("<null>"),這和其他對(duì)象一樣,而打印 nil 的時(shí)候我們得到的是普通括號(hào)((null)):

outside animation block: <null>
inside animation block: <CABasicAnimation: 0x8c2ff10>

對(duì)于 view 中的 layer 來說,對(duì)動(dòng)作的搜索只會(huì)到第一步為止(至少我沒有見過 view 返回一個(gè) nil 然后導(dǎo)致繼續(xù)搜索動(dòng)作的情況)。對(duì)于單獨(dú)的 layer 來說,剩余的四個(gè)步驟可以在 CALayer 的 actionForKey: 文檔中找到。

從 UIKit 中學(xué)習(xí)

我很確定我們都會(huì)同意 UIView 動(dòng)畫是一組非常優(yōu)秀的 API,它簡(jiǎn)潔明確。實(shí)際上,它使用了 Core Animation 來執(zhí)行動(dòng)畫,這給了我們一個(gè)絕佳的機(jī)會(huì)來深入研究 UIKit 是如何使用 Core Animation 的。在這里甚至還有很多非常棒的實(shí)踐和技巧可以讓我們借鑒。:)

當(dāng)屬性在動(dòng)畫 block 中改變時(shí),view 將向 layer 返回一個(gè)基本的動(dòng)畫,然后動(dòng)畫通過通常的 addAnimation:forKey: 方法被添加到 layer 中,就像顯式地添加動(dòng)畫那樣。再一次,別直接信我,讓我們實(shí)踐檢驗(yàn)一下。

歸功于 UIView 的 +layerClass 類方法,view 和 layer 之間的交互很容易被觀測(cè)到。通過這個(gè)方法我們可以在為 view 創(chuàng)建 layer 時(shí)為其指定要使用的類。通過子類一個(gè) UIView,以及用這個(gè)方法返回一個(gè)自定義的 layer 類,我們就可以重寫 layer 子類中的 addAnimation:forKey: 并輸出一些東西來驗(yàn)證它是否確實(shí)被調(diào)用。唯一要記住的是我們需要調(diào)用 super 方法,不然的話我們就把要觀測(cè)的行為完全改變了:

@interface DRInspectionLayer : CALayer
@end

@implementation DRInspectionLayer
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
{
    NSLog(@"adding animation: %@", [anim debugDescription]);
    [super addAnimation:anim forKey:key];
}
@end

@interface DRInspectionView : UIView
@end

@implementation DRInspectionView
+ (Class)layerClass
{
    return [DRInspectionLayer class];
}
@end

通過輸出動(dòng)畫的 debug 信息,我們不僅可以驗(yàn)證它確實(shí)如預(yù)期一樣被調(diào)用了,還可以看到動(dòng)畫是如何組織構(gòu)建的:

<CABasicAnimation:0x8c73680; 
    delegate = <UIViewAnimationState: 0x8e91fa0>;
    fillMode = both; 
    timingFunction = easeInEaseOut; 
    duration = 0.3; 
    fromValue = NSPoint: {5, 5}; 
    keyPath = position
>

當(dāng)動(dòng)畫剛被添加到 layer 時(shí),屬性的新值還沒有被改變。在構(gòu)建動(dòng)畫時(shí),只有 fromValue (也就是當(dāng)前值) 被顯式地指定了。CABasicAnimation 的文檔向我們簡(jiǎn)單介紹了這么做對(duì)于動(dòng)畫的插值來說的的行為應(yīng)該是:

只有 fromValue 不是 nil 時(shí),在 fromValue 和屬性當(dāng)前顯示層的值之間進(jìn)行插值。

這也是我在處理顯式動(dòng)畫時(shí)選擇的做法,將一個(gè)屬性改變?yōu)樾碌闹?,然后將?dòng)畫對(duì)象添加到 layer 上:

CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.duration  = 0.75;
fadeIn.fromValue = @0;

myLayer.opacity = 1.0; // 更改 model 的值 ...
// ... 然后添加動(dòng)畫對(duì)象
[myLayer addAnimation:fadeIn forKey:@"fade in slowly"];

這很簡(jiǎn)潔,你也不需要在動(dòng)畫被移除的時(shí)候做什么額外操作。如果動(dòng)畫是在一段延遲后才開始的話,你可以使用 backward 填充模式 (或者 'both' 填充模式),就像 UIKit 所創(chuàng)建的動(dòng)畫那樣。

可能你看見上面輸出中的動(dòng)畫的 delegate 了,想知道這個(gè)類是用來做什么的嗎?我們可以來看看 dump 出來的頭文件,它主要用來維護(hù)動(dòng)畫的一些狀態(tài) (持續(xù)時(shí)間,延時(shí),重復(fù)次數(shù)等等)。它還負(fù)責(zé)對(duì)一個(gè)棧做 push 和 pop,這是為了在多個(gè)動(dòng)畫 block 嵌套時(shí)能夠獲取正確的動(dòng)畫狀態(tài)。這些都是些實(shí)現(xiàn)細(xì)節(jié),除非你想要寫一套自己的基于 block 的動(dòng)畫 API,否則可能你不會(huì)用到它們 (實(shí)際上這是一個(gè)很有趣的點(diǎn)子)。

然后真正_有意思_的是這個(gè) delegate 實(shí)現(xiàn)了 animationDidStart:animationDidStop:finished:,并將信息傳給了它自己的 delegate。

編者注 這里不太容易理解,加以說明:從上面的頭文件中可以看出,作為 CAAnimation 的 delegate 的私有類 `UIViewAnimationState` 中還有一個(gè) `_delegate` 成員,并且 `animationDidStart:` 和 `animationDidStop:finished:` 也是典型的 delegate 的實(shí)現(xiàn)方法。

通過打印這個(gè) delegate 的 delegate,我們可以發(fā)現(xiàn)它也是一個(gè)私有類:UIViewAnimationBlockDelegate。同樣進(jìn)行 class dump 得到它的頭文件,這是一個(gè)很小的類,只負(fù)責(zé)一件事情:響應(yīng)動(dòng)畫的 delegate 回調(diào)并且執(zhí)行相應(yīng)的 block。如果我們使用自己的 Core Animation 代碼,并且選擇 block 而不是 delegate 做回調(diào)的話,添加這個(gè)是很容易的:

@interface DRAnimationBlockDelegate : NSObject

@property (copy) void(^start)(void);
@property (copy) void(^stop)(BOOL);

+(instancetype)animationDelegateWithBeginning:(void(^)(void))beginning
                                   completion:(void(^)(BOOL finished))completion;

@end

@implementation DRAnimationBlockDelegate

+ (instancetype)animationDelegateWithBeginning:(void (^)(void))beginning
                                    completion:(void (^)(BOOL))completion
{
    DRAnimationBlockDelegate *result = [DRAnimationBlockDelegate new];
    result.start = beginning;
    result.stop  = completion;
    return result;
}

- (void)animationDidStart:(CAAnimation *)anim
{
    if (self.start) {
        self.start();
    }
    self.start = nil;
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (self.stop) {
        self.stop(flag);
    }
    self.stop = nil;
}

@end

雖然是我個(gè)人的喜好,但是我覺得像這樣的基于 block 的回調(diào)風(fēng)格可能會(huì)比實(shí)現(xiàn)一個(gè) delegate 回調(diào)更適合你的代碼:

fadeIn.delegate = [DRAnimationBlockDelegate animationDelegateWithBeginning:^{
    NSLog(@"beginning to fade in");
} completion:^(BOOL finished) {
    NSLog(@"did fade %@", finished ? @"to the end" : @"but was cancelled");
}];

自定義基于 block 的動(dòng)畫 APIs

一旦你知道了 actionForKey: 的機(jī)理之后,UIView 就遠(yuǎn)沒有它一開始看起來那么神秘了。實(shí)際上我們完全可以按照我們的需求量身定制地寫出一套自己的基于 block 的動(dòng)畫 APIs。我所設(shè)計(jì)的動(dòng)畫將通過在 block 中用一個(gè)很激進(jìn)的時(shí)間曲線來做動(dòng)畫,以吸引用戶對(duì)該 view 的注意,之后做一個(gè)緩慢的動(dòng)畫回到原始狀態(tài)。你可以把它看作一種類似 pop (請(qǐng)不要和 Facebook 最新的 Pop 框架弄混了)的行為。與一般使用 UIViewAnimationOptionAutoreverse 的動(dòng)畫 block 不同,因?yàn)閯?dòng)畫設(shè)計(jì)和概念上的需要,我自己實(shí)現(xiàn)了將 model 值改變回原始值的過程。自定義的動(dòng)畫 API 的使用方法就像這樣:

[UIView DR_popAnimationWithDuration:0.7
                             animations:^{
                                 myView.transform = CGAffineTransformMakeRotation(M_PI_2);
                                }];

當(dāng)我們完成后,效果是這個(gè)樣子的 (對(duì)四個(gè)不同的 view 為位置,尺寸,顏色和旋轉(zhuǎn)進(jìn)行動(dòng)畫):

http://wiki.jikexueyuan.com/project/objc/images/12-23.gif" alt="" />

要開始實(shí)現(xiàn)它,我們首先要做的是當(dāng)一個(gè) layer 屬性變化時(shí)獲取 delegate 的回調(diào)。因?yàn)槲覀儫o法事先預(yù)測(cè) layer 要改變什么,所以我選擇在一個(gè) UIView 的 category 中 swizzle actionForLayer:forKey: 方法:

@implementation UIView (DR_CustomBlockAnimations)

+ (void)load
{        
    SEL originalSelector = @selector(actionForLayer:forKey:);
    SEL extendedSelector = @selector(DR_actionForLayer:forKey:);

    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method extendedMethod = class_getInstanceMethod(self, extendedSelector);

    NSAssert(originalMethod, @"original method should exist");
    NSAssert(extendedMethod, @"exchanged method should exist");

    if(class_addMethod(self, originalSelector, method_getImplementation(extendedMethod), method_getTypeEncoding(extendedMethod))) {
        class_replaceMethod(self, extendedSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, extendedMethod);
    }
}

為了保證我們不破壞其他依賴于 actionForLayer:forKey: 回調(diào)的代碼,我們使用一個(gè)靜態(tài)變量來判斷現(xiàn)在是不是處于我們自己定義的上下文中。對(duì)于這個(gè)例子來說一個(gè)簡(jiǎn)單的 BOOL 其實(shí)就夠了,但是如果我們之后要寫更多內(nèi)容的話,上下文的話就要靈活得多了:

static void *DR_currentAnimationContext = NULL;
static void *DR_popAnimationContext     = &DR_popAnimationContext;

- (id<CAAction>)DR_actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    if (DR_currentAnimationContext == DR_popAnimationContext) {
        // 這里寫我們自定義的代碼...
    }

    // 調(diào)用原始方法
    return [self DR_actionForLayer:layer forKey:event]; // 沒錯(cuò),你沒看錯(cuò)。因?yàn)樗鼈円呀?jīng)被交換了
}

在我們的實(shí)現(xiàn)中,我們要確保在執(zhí)行動(dòng)畫 block 之前設(shè)置動(dòng)畫的上下文,并且在執(zhí)行后恢復(fù)上下文:

 + (void)DR_popAnimationWithDuration:(NSTimeInterval)duration
                          animations:(void (^)(void))animations
 {
     DR_currentAnimationContext = DR_popAnimationContext;
     // 執(zhí)行動(dòng)畫 (它將觸發(fā)交換后的 delegate 方法)
     animations();
     /* 一會(huì)兒再添加 */
     DR_currentAnimationContext = NULL;
 }

如果我們想要做的不過是添加一個(gè)從舊的值向新的值過度的動(dòng)畫的話,我們可以直接在 delegate 的回調(diào)中來做。然而因?yàn)槲覀兿胍_地控制動(dòng)畫,我們需要用一個(gè)幀動(dòng)畫來實(shí)現(xiàn)。幀動(dòng)畫需要所有的值都是已知的,而對(duì)我們的情況來說,新的值還沒有被設(shè)定,因此我們也就無從知曉。

有意思的是,iOS 添加的一個(gè)基于 block 的動(dòng)畫 API 也遇到了同樣的問題。使用和上面一樣的觀察手段,我們就能知道它是如何繞開這個(gè)麻煩的。對(duì)于每個(gè)關(guān)鍵幀,在屬性變化時(shí),view 返回 nil,但是卻存儲(chǔ)下需要的狀態(tài)。這樣就能在所有關(guān)鍵幀 block 執(zhí)行后創(chuàng)建一個(gè) CAKeyframeAnimationz 對(duì)象。

受到這種方法的啟發(fā),我們可以創(chuàng)建一個(gè)小的類來存儲(chǔ)我們創(chuàng)建動(dòng)畫時(shí)所需要的信息:什么 layer 被更改了,什么 key path 的值被改變了,以及原來的值是什么:

 @interface DRSavedPopAnimationState : NSObject

 @property (strong) CALayer  *layer;
 @property (copy)   NSString *keyPath;
 @property (strong) id        oldValue;

 + (instancetype)savedStateWithLayer:(CALayer *)layer
                             keyPath:(NSString *)keyPath;

 @end

 @implementation DRSavedPopAnimationState

 + (instancetype)savedStateWithLayer:(CALayer *)layer
                             keyPath:(NSString *)keyPath
 {
     DRSavedPopAnimationState *savedState = [DRSavedPopAnimationState new];
     savedState.layer    = layer;
     savedState.keyPath  = keyPath;
     savedState.oldValue = [layer valueForKeyPath:keyPath];
     return savedState;
 }

 @end

接下來,在我們的交換后的 delegate 回調(diào)中,我們簡(jiǎn)單地將被變更的屬性的狀態(tài)存入一個(gè)靜態(tài)可變數(shù)組中:

 if (DR_currentAnimationContext == DR_popAnimationContext) {
       [[UIView DR_savedPopAnimationStates] addObject:[DRSavedPopAnimationState savedStateWithLayer:layer
                                                                                 keyPath:event]];

       // 沒有隱式的動(dòng)畫 (稍后添加)
       return (id<CAAction>)[NSNull null];
   }

在動(dòng)畫 block 執(zhí)行完畢后,所有的屬性都被變更了,它們的狀態(tài)也被保存了。現(xiàn)在,創(chuàng)建關(guān)鍵幀動(dòng)畫:

 + (void)DR_popAnimationWithDuration:(NSTimeInterval)duration
                          animations:(void (^)(void))animations
 {
     DR_currentAnimationContext = DR_popAnimationContext;

     // 執(zhí)行動(dòng)畫 (它將觸發(fā)交換后的 delegate 方法)
     animations();

     [[self DR_savedPopAnimationStates] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
         DRSavedPopAnimationState *savedState   = (DRSavedPopAnimationState *)obj;
         CALayer *layer    = savedState.layer;
         NSString *keyPath = savedState.keyPath;
         id oldValue       = savedState.oldValue;
         id newValue       = [layer valueForKeyPath:keyPath];

         CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:keyPath];

         CGFloat easing = 0.2;
         CAMediaTimingFunction *easeIn  = [CAMediaTimingFunction functionWithControlPoints:1.0 :0.0 :(1.0-easing) :1.0];
         CAMediaTimingFunction *easeOut = [CAMediaTimingFunction functionWithControlPoints:easing :0.0 :0.0 :1.0];

         anim.duration = duration;
         anim.keyTimes = @[@0, @(0.35), @1];
         anim.values = @[oldValue, newValue, oldValue];
         anim.timingFunctions = @[easeIn, easeOut];

         // 不帶動(dòng)畫地返回原來的值
         [CATransaction begin];
         [CATransaction setDisableActions:YES];
         [layer setValue:oldValue forKeyPath:keyPath];
         [CATransaction commit];

         // 添加 "pop" 動(dòng)畫
         [layer addAnimation:anim forKey:keyPath];

     }];

     // 掃除工作 (移除所有存儲(chǔ)的狀態(tài))
     [[self DR_savedPopAnimationStates] removeAllObjects];

     DR_currentAnimationContext = nil;
 }

注意老的 model 值被設(shè)到了 layer 上,所以在當(dāng)動(dòng)畫結(jié)束和移除后,model 的值和 presentation 的值是相符合的。

創(chuàng)建像這樣的你自己的 API 不會(huì)對(duì)每種情況都很適合,但是如果你需要在你的應(yīng)用中的很多地方都做同樣的動(dòng)畫的話,這可以幫助你寫出整潔的代碼,并減少重復(fù)。就算你之后從來不會(huì)使用這種方法,實(shí)際做一遍也能幫助你搞懂 UIView block 動(dòng)畫的 APIs,特別是你已經(jīng)在 Core Animation 的舒適區(qū)的時(shí)候,這非常有助于你的提高。

其他的動(dòng)畫靈感

UIImageView 動(dòng)畫是一個(gè)完全不同的更高層次的動(dòng)畫 API 的實(shí)現(xiàn)方式,我會(huì)把它留給你來探索。表面上,它只不過是重新組裝了一個(gè)傳統(tǒng)的動(dòng)畫 API。你所要做的事情就是指定一個(gè)圖片數(shù)組和一段時(shí)間,然后告訴 image view 開始動(dòng)畫。在抽象背后,其實(shí)是一個(gè)添加在 image view 的 layer 上的 contents 屬性的離散的關(guān)鍵幀動(dòng)畫:

<CAKeyframeAnimation:0x8e5b020; 
    removedOnCompletion = 0; 
    delegate = <_UIImageViewExtendedStorage: 0x8e49230>; 
    duration = 2.5; 
    repeatCount = 2.14748e+09; 
    calculationMode = discrete; 
    values = (
        "<CGImage 0x8d6ce80>",
        "<CGImage 0x8d6d2d0>",
        "<CGImage 0x8d5cd30>"
    ); 
    keyPath = contents
>

動(dòng)畫 APIs 可以以很多不同形式出現(xiàn),而對(duì)于你自己寫的動(dòng)畫 API 來說,也是這樣的。