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

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

交互式動畫

在2007年,喬布斯在第一次介紹 iPhone 的時候,iPhone 的觸摸屏交互簡直就像是一種魔法。最好的例子就是在他第一次滑動 TableView 的展示上。你可以感受到當時觀眾的反應(yīng)是多么驚訝,但是對于現(xiàn)在的我們來說早已習以為常。在展示的后面一部分,他特別指出當他給別人看了這個滑動例子,別人說的一句話: “當這個界面滑動的時候我就已經(jīng)被征服了”.

是什么樣的滑動能讓人有‘哇哦’的效果呢?

滑動是最完美地展示了通過觸摸屏直接操作的例子。滾動視圖遵從于你的手指,當你的手指離開屏幕的時,視圖會自然地繼續(xù)滑動直到該停止的時候停止。它用自然的方式減速,甚至在快到界限的時候也能表現(xiàn)出細膩的彈力效果?;瑒釉谌魏螘r候都保持相應(yīng),并且看上去非常真實。

動畫的狀態(tài)

在 iOS 中的大部分動畫仍然沒有按照最初 iPhone 指定的滑動標準實現(xiàn)。這里有很多動畫一旦它們運行就不能交互(比如說解鎖動畫,主界面中打開文件夾和關(guān)閉文件夾的動畫,和導(dǎo)航欄切換的動畫,還有很多)。

然而現(xiàn)在有一些應(yīng)用給我一種始終在控制動畫的體驗,我們可以直接操作那些我在用的動畫。當我們將這些應(yīng)用和其他的應(yīng)用相比較之后,我們就能感覺到明顯的區(qū)別。這些應(yīng)用中最優(yōu)秀的有最初的 Twitter iPad app, 和現(xiàn)在的 Facebook Paper。但目前,使用直接操作為主并且可以中斷動畫的應(yīng)用仍然很少。這就給我們做出更好的應(yīng)用提供了機會,讓我們的應(yīng)用有更不同的,更高質(zhì)量的體驗。

真實交互式動畫的挑戰(zhàn)

當我們用 UIView 或者 CAAnimation 來實現(xiàn)交互式動畫時會有兩個大問題: 這些動畫會將你在屏幕上的內(nèi)容和 layer 上的實際的特定屬性分離開來,并且他們直接操作這些特定屬性。

模型 (Model) 和顯示 (Presentation) 的分離

Core Animation 是通過分離 layer 的模型屬性和你在屏幕上看到的界面 (顯示層) 的方式來設(shè)計的,這就導(dǎo)致我們很難去創(chuàng)建一個可以在任何時候能交互的動畫,因為在動畫時,模型和界面已經(jīng)不能匹配了。這時,我們不得不通過手動的方式來同步這兩個的狀態(tài),來達到改變動畫的效果:

view.layer.center = view.layer.presentationLayer.center;
[view.layer removeAnimationForKey:@"animation"];
// 添加新動畫

直接控制 vs 間接控制

CAAnimation 動畫的更大的問題是它們是直接在 layer 上對屬性進行操作的。這意味著什么呢?比如我們想指定一個 layer 從坐標為 (100, 100) 的位置運動到 (300, 300) 的位置,但是在它運動到中間的時候,我們想它停下來并且讓它回到它原來的位置,事情就變得非常復(fù)雜了。如果你只是簡單地刪除當前的動畫然后再添加一個新的,那么這個 layer 的速率就會不連續(xù)。

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

然而,我們想要的是一個漂亮的,流暢地減速和加速的動畫。

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

只有通過間接操作動畫才能達到上面的效果,比如通過模擬力在界面上的表現(xiàn)。新的動畫需要用 layer 的當前速度矢量作為參數(shù)傳入來達到流暢的效果。

看一下 UIView 中關(guān)于彈簧動畫的 API (animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:),你會注意到速率是個 CGFloat。所以當我們給一個移動 view 的動畫在其運動的方向上加一個初始的速率時,你沒法告知動畫這個 view 現(xiàn)在的運動狀態(tài),比如我們不知道要添加的動畫的方向是不是和原來的 view 的速度方向垂直。為了使其成為可能,這個速度需要用向量來表示。

解決方案

讓我們看一下我們怎樣來正確實現(xiàn)一個可交互并且可以中斷的動畫。我們來做一個類似于控制中心板的東西來實現(xiàn)這個效果:

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

這個控制板有兩個狀態(tài):打開和關(guān)閉。你可以通過點擊來切換這兩個狀態(tài),或者通過上下拖動來調(diào)調(diào)整它向上或向下。我要將這個控制面板的所有狀態(tài)都做到可以交互,甚至是在動畫的過程中也可以,這是一個很大的挑戰(zhàn)。比如,當你在這個控制板還沒有切換到打開狀態(tài)的動畫過程中,你點擊了它,那么它應(yīng)該從現(xiàn)在這個點的位置馬上回到關(guān)閉狀態(tài)的位置。在現(xiàn)在很多的應(yīng)用中,大部分都是用默認的動畫 API,你必須要等一個動畫結(jié)束之后你才能做自己想做的事情?;蛘?,如果你不等待的話,就會看到一個不連續(xù)的速度曲線。我們要解決這個問題。

UIKit 力學(xué)

隨著 iOS7 的發(fā)布,蘋果向我們展示了一個叫 UIKit 力學(xué)的動畫框架 (可以參見 WWDC 2013 sessions 206221)。UIKit 力學(xué)是一個基于模擬物理引擎的框架,只要你添加指定的行為到動畫對象上來實現(xiàn) UIDynamicItem 協(xié)議就能實現(xiàn)很多動畫。這個框架非常強大,并且它能夠在多個物體間啟用像是附著和碰撞這樣的復(fù)雜行為。請看一下 UIKit Dynamics Catalog,確認一下什么是可用的。

因為 UIKit 力學(xué)中的的動畫是被間接驅(qū)動的,就像我在上面提到的,這使我們實現(xiàn)真實的交互式動畫成為可能,它能在任何時候被中斷并且擁有連續(xù)的加速度。同時,UIKit 力學(xué)在物理層的抽象上能完全勝任我們一般情況下在用戶界面中的所需要的所有動畫。其實在大部分情況下,我們只會用到其中的一小部分功能。

定義行為

為了實現(xiàn)我們的控制板的行為,我們將使用 UIkit 力學(xué)中的兩個不同行為:UIAttachmentBehaviorUIDynamicItemBehavior。附著行為用來扮演彈簧的角色,它將界面向目標點拉動。另一方面,我們用動態(tài) item behvaior 定義了比如摩擦系數(shù)這樣的界面的內(nèi)置屬性。

我創(chuàng)建了一個我們自己的行為子類,以將這兩個行為封裝到我們的控制板上:

@interface PaneBehavior : UIDynamicBehavior

@property (nonatomic) CGPoint targetPoint;
@property (nonatomic) CGPoint velocity;

- (instancetype)initWithItem:(id <UIDynamicItem>)item;

@end

我們通過一個 dynamic item 來初始化這個行為,然后就可以設(shè)置它的目標點和我們想要的任何速度。在內(nèi)部,我們創(chuàng)建了附著行為和 dynamic item 行為,并且將這些行為添加到我們自定義的行為中:

- (void)setup
{
    UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.item attachedToAnchor:CGPointZero];
    attachmentBehavior.frequency = 3.5;
    attachmentBehavior.damping = .4;
    attachmentBehavior.length = 0;
    [self addChildBehavior:attachmentBehavior];
    self.attachmentBehavior = attachmentBehavior;

    UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.item]];
    itemBehavior.density = 100;
    itemBehavior.resistance = 10;
    [self addChildBehavior:itemBehavior];
    self.itemBehavior = itemBehavior;
}

為了用 targetPointvelocity 屬性來影響 item 的 behavior,我們需要重寫它們的 setter 方法,并且分別修改在附著行為和 item behaviors 中的對應(yīng)的屬性。我們對目標點的 setter 方法來說,這個改動很簡單:

- (void)setTargetPoint:(CGPoint)targetPoint
{
    _targetPoint = targetPoint;
    self.attachmentBehavior.anchorPoint = targetPoint;
}

對于 velocity 屬性,我們需要多做一些工作,因為 dynamic item behavior 只允許相對地改變速度。這就意味如果我們要將 velocity 設(shè)置為絕對值,首先我們就需要得到當前的速度,然后再加上速度差才能得到我們的目標速度。

- (void)setVelocity:(CGPoint)velocity
{
    _velocity = velocity;
    CGPoint currentVelocity = [self.itemBehavior linearVelocityForItem:self.item];
    CGPoint velocityDelta = CGPointMake(velocity.x - currentVelocity.x, velocity.y - currentVelocity.y);
    [self.itemBehavior addLinearVelocity:velocityDelta forItem:self.item];
}

將Behavior投入使用

我們的控制板有三個不同狀態(tài):在開始或結(jié)束位置的靜止狀態(tài),正在被用戶拖動的狀態(tài),以及在沒有用戶控制時運動到結(jié)束位置的動畫狀態(tài)。

為了將從直接操作狀態(tài) (用戶拖動這個滑動板) 過渡到動畫狀態(tài)這個過程做的流暢,我們還有很多其他的事要做。當用戶停止拖動控制板時,它會發(fā)送一個消息到它的 delegate。根據(jù)這個方法,我們可以知道這個板應(yīng)該朝哪個方向運動,然后在我們自定義的 PaneBehavior 上設(shè)置結(jié)束點,以及初始速度 (這非常重要),并將行為添加到動畫器中去,以此確保從拖動操作到動畫狀態(tài)這個過程能夠非常流暢。

- (void)draggableView:(DraggableView *)view draggingEndedWithVelocity:(CGPoint)velocity
{
    PaneState targetState = velocity.y >= 0 ? PaneStateClosed : PaneStateOpen;
    [self animatePaneToState:targetState initialVelocity:velocity];
}

- (void)animatePaneToState:(PaneState)targetState initialVelocity:(CGPoint)velocity
{
    if (!self.paneBehavior) {
        PaneBehavior *behavior = [[PaneBehavior alloc] initWithItem:self.pane];
        self.paneBehavior = behavior;
    }
    self.paneBehavior.targetPoint = [self targetPointForState:targetState];
    if (!CGPointEqualToPoint(velocity, CGPointZero)) {
        self.paneBehavior.velocity = velocity;
    }
    [self.animator addBehavior:self.paneBehavior];
    self.paneState = targetState;
}

一旦用戶用他的手指再次觸動控制板時,我必須要將所有的 dynamic behavior 從 animator 刪除,這樣才不會影響控制板對拖動手勢的響應(yīng):

- (void)draggableViewBeganDragging:(DraggableView *)view
{
    [self.animator removeAllBehaviors];
}

我們不僅僅允許控制板可以被拖動,還要允許它可以被點擊,讓它可以從一個位置跳轉(zhuǎn)到另一個位置以達到開關(guān)的效果。一旦點擊事件發(fā)生,我們就會立即調(diào)整這個滑動板的目標位置。因為我們不能直接控制動畫,但是通過彈力和摩擦力,我們的動畫可以非常流暢地執(zhí)行這個動作:

- (void)didTap:(UITapGestureRecognizer *)tapRecognizer
{
    PaneState targetState = self.paneState == PaneStateOpen ? PaneStateClosed : PaneStateOpen;
    [self animatePaneToState:targetState initialVelocity:CGPointZero];
}

這樣就實現(xiàn)了我們的大部分功能了。你可以在 GitHub 上查看完整的例子。

重申一點:UIKit 力學(xué)可以通過在界面上模擬力來間接地驅(qū)動動畫(我們的例子中,使用的是彈力和摩擦力)。這間接地使我們在任何時候都能以連續(xù)的速度曲線來與界面進行交互。

現(xiàn)在我們已經(jīng)通過 UIKit 力學(xué)實現(xiàn)了整個交互,讓我們回顧一下這個場景。這個例子的動畫中我們只用了 UIKit 力學(xué)中一小部分功能,并且它的實現(xiàn)方式也非常簡單。對于我們來說這是一個去理解它其中的過程的很好的例子,但是如果我們使用的環(huán)境中沒有 UIKit 力學(xué) (比如說在 Mac 上),或者你的使用場景中不能很好的適用 UIKit 力學(xué)呢。

自己操作動畫

至于在你的應(yīng)用中大部分時間會用的動畫,比如簡單的彈力動畫,我們控制它真的不難。我們可以做一個練習,來看看如何拋棄 UIKit 力學(xué)這個巨大的黑盒子,看要如何“手動”來實現(xiàn)一個簡單的交互。想法非常簡單:我們只要每秒修改這個 view 的 frame 60 次。每一幀我們都基于當前速度和作用在 view 上的力來調(diào)整 view 的 frame 就可以了。

物理原理

首先讓我們看一下我們需要知道的基礎(chǔ)物理知識,這樣我們才能實現(xiàn)出剛才使用 UIKit 力學(xué)實現(xiàn)的那種彈簧動畫效果。為了簡化問題,雖然引入第二個維度也是很直接的,但我們在這里只關(guān)注一維的情況 (在我們的例子中就是這樣的情況)。

我們的目標是依據(jù)控制面板當前的位置和上一次動畫后到現(xiàn)在為止經(jīng)過的時間,來計算它的新位置。我們可以把它表示成這樣:

y = y0 + Δy

位置的偏移量可以通過速度和時間的函數(shù)來表達:

Δy = v ? Δt

這個速度可以通過前一次的速度加上速度偏移量算出來,這個速度偏移量是由力在 view 上的作用引起的。

v = v0 + Δv

速度的變化可以通過作用在這個 view 上的沖量計算出來:

Δv = (F ? Δt) / m

現(xiàn)在,讓我們看一下作用在這個界面上的力。為了得到彈簧效果,我們必須要將摩擦力和彈力結(jié)合起來:

F = F_spring + F_friction

彈力的計算方法我們可以從任何一本教科書中得到 (編者注:簡單的胡克定律):

F_spring = k ? x

k 是彈簧的勁度系數(shù),x 是 view 到目標結(jié)束位置的距離 (也就是彈簧的長度)。因此,我們可以把它寫成這樣:

F_spring = k ? abs(y_target - y0)

摩擦力和 view 的速度成正比:

F_friction = μ ? v

μ 是一個簡單的摩擦系數(shù)。你可以通過別的方式來計算摩擦力,但是這個方法能很好地做出我們想要的動畫效果。

將上面的表達式放在一起,我們就可以算出作用在界面上的力:

F = k ? abs(y_target - y0) + μ ? v

為了實現(xiàn)起來更簡單點些,我們將 view 的質(zhì)量設(shè)為 1,這樣我們就能計算在位置上的變化:

Δy = (v0 + (k ? abs(y_target - y0) + μ ? v) ? Δt) ? Δt

實現(xiàn)動畫

為了實現(xiàn)這個動畫,我們首先需要創(chuàng)建我們自己的 Animator 類,它將扮演驅(qū)動動畫的角色。這個類使用了 CADisplayLink,CADisplayLink 是專門用來將繪圖與屏幕刷新頻率相同步的定時器。換句話說,如果你的動畫是流暢的,這個定時器就會每秒調(diào)用你的方法60次。接下來,我們需要實現(xiàn) Animation 協(xié)議來和我們的 Animator 一起工作。這個協(xié)議只有一個方法,animationTick:finished:。屏幕每次被刷新時都會調(diào)用這個方法,并且在方法中會得到兩個參數(shù):第一個參數(shù)是前一個 frame 的持續(xù)時間,第二個參數(shù)是一個指向 BOOL 的指針。當我們設(shè)置這個指針的值為 YES 時,我們就可以與 Animator 取得通訊并匯報動畫完成;

@protocol Animation <NSObject>
- (void)animationTick:(CFTimeInterval)dt finished:(BOOL *)finished;
@end

我們會在下面實現(xiàn)這個方法。首先,根據(jù)時間間隔我們來計算由彈力和摩擦力的合力。然后根據(jù)這個力來更新速度,并調(diào)整 view 的中心位置。最后,當這個速度降低并且 view 到達結(jié)束位置時,我們就停止這個動畫:

- (void)animationTick:(CFTimeInterval)dt finished:(BOOL *)finished
{
    static const float frictionConstant = 20;
    static const float springConstant = 300;
    CGFloat time = (CGFloat) dt;

    //摩擦力 = 速度 * 摩擦系數(shù)
    CGPoint frictionForce = CGPointMultiply(self.velocity, frictionConstant);
    //彈力 = (目標位置 - 當前位置) * 彈簧勁度系數(shù)
    CGPoint springForce = CGPointMultiply(CGPointSubtract(self.targetPoint, self.view.center), springConstant);
    //力 = 彈力 - 摩擦力
    CGPoint force = CGPointSubtract(springForce, frictionForce);

    //速度 = 當前速度 + 力 * 時間 / 質(zhì)量
    self.velocity = CGPointAdd(self.velocity, CGPointMultiply(force, time));
    //位置 = 當前位置 + 速度 * 時間
    self.view.center = CGPointAdd(self.view.center, CGPointMultiply(self.velocity, time));

    CGFloat speed = CGPointLength(self.velocity);
    CGFloat distanceToGoal = CGPointLength(CGPointSubtract(self.targetPoint, self.view.center));
    if (speed < 0.05 && distanceToGoal < 1) {
        self.view.center = self.targetPoint;
        *finished = YES;
    }
}

這就是這個方法里的全部內(nèi)容。我們把這個方法封裝到一個 SpringAnimation 對象中。除了這個方法之外,這個對象中還有一個初始化方法,它指定了 view 中心的目標位置 (在我們的例子中,就是打開狀態(tài)時界面的中心位置,或者關(guān)閉狀態(tài)時界面的中心位置) 和初始的速度。

將動畫添加到 view 上

我們的 view 類剛好和使用 UIDynamic 的例子一樣:它有一個拖動手勢,并且根據(jù)拖動手勢來更新中心位置。它也有兩個同樣的 delegate 方法,這兩個方法會實現(xiàn)動畫的初始化。首先,一旦用戶開始拖動控制板時,我們就取消所有動畫:

- (void)draggableViewBeganDragging:(DraggableView *)view
{
    [self cancelSpringAnimation];
}

一旦停止拖動,我們就根據(jù)從拖動手勢中得到的最后一個速率值來開始我們的動畫。我們根據(jù)拖動狀態(tài) paneState 計算出動畫的結(jié)束位置:

- (void)draggableView:(DraggableView *)view draggingEndedWithVelocity:(CGPoint)velocity
{
    PaneState targetState = velocity.y >= 0 ? PaneStateClosed : PaneStateOpen;
    self.paneState = targetState;
    [self startAnimatingView:view initialVelocity:velocity];
}

- (void)startAnimatingView:(DraggableView *)view initialVelocity:(CGPoint)velocity
{
    [self cancelSpringAnimation];
    self.springAnimation = [UINTSpringAnimation animationWithView:view target:self.targetPoint velocity:velocity];
    [view.animator addAnimation:self.springAnimation];
}

剩下來要做的就是添加點擊動畫了,這很簡單。一旦我們觸發(fā)這個狀態(tài),就開始動畫。如果這里正在進行彈簧動畫,我們就用當時的速度作為開始。如果這個彈簧動畫是 nil,那么這個開始速度就是 CGPointZero。想要知道為什么依然可以進行動畫,可以看看 animationTick:finished: 里的代碼。當這個起始速度為 0 的時候,彈力就會使速度緩慢地增長,直到面板到達目標位置:

- (void)didTap:(UITapGestureRecognizer *)tapRecognizer
{
    PaneState targetState = self.paneState == PaneStateOpen ? PaneStateClosed : PaneStateOpen;
    self.paneState = targetState;
    [self startAnimatingView:self.pane initialVelocity:self.springAnimation.velocity];
}

動畫驅(qū)動

最后,我們需要一個 Animator,也就是動畫的驅(qū)動者。Animator 封裝了 display link。因為每個 display link 都鏈接一個指定的 UIScreen,所以我們根據(jù)這個指定的 UIScreen 來初始化我們的 animator。我們初始化一個 display link,并且將它加入到 run loop 中。因為現(xiàn)在還沒有動畫,所以我們是從暫停狀態(tài)開始的:

- (instancetype)initWithScreen:(UIScreen *)screen
{
    self = [super init];
    if (self) {
        self.displayLink = [screen displayLinkWithTarget:self selector:@selector(animationTick:)];
        self.displayLink.paused = YES;
        [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        self.animations = [NSMutableSet new];
    }
    return self;
}

一旦我們添加了這個動畫,我們要確保這個 display link 不再是停止狀態(tài):

- (void)addAnimation:(id<Animation>)animation
{
    [self.animations addObject:animation];
    if (self.animations.count == 1) {
        self.displayLink.paused = NO;
    }
}

我們設(shè)置這個 display link 來調(diào)用 animationTick: 方法,在每個 Tick 中,我們都遍歷它的動畫數(shù)組,并且給這些動畫數(shù)組中的每個動畫發(fā)送一個消息。如果這個動畫數(shù)組中已經(jīng)沒有動畫了,我們就暫停這個 display link。

 - (void)animationTick:(CADisplayLink *)displayLink
 {
     CFTimeInterval dt = displayLink.duration;
     for (id<Animation> a in [self.animations copy]) {
         BOOL finished = NO;
         [a animationTick:dt finished:&finished];
         if (finished) {
             [self.animations removeObject:a];
         }
     }
     if (self.animations.count == 0) {
         self.displayLink.paused = YES;
     }
 }

完整的項目在 GitHub 上。

權(quán)衡

我們必須記住,通過 display link 來驅(qū)動動畫 (就像我們剛才演示的例子,或者我們使用UIkit力學(xué)來做的例子,又或者是使用 Facebook 的 Pop 框架) 是有代價需要進行權(quán)衡的。就像 Andy Matuschar 指出的那樣,UIView 和 CAAnimation 動畫比其他任務(wù)更少受系統(tǒng)的影響,因為比起你的應(yīng)用來說,渲染處于更高的優(yōu)先級。

回到 Mac

現(xiàn)在 Mac 中還沒有 UIKit 力學(xué)。如果你想在 Mac 中創(chuàng)建一個真實的交互式動畫,你必須自己去實現(xiàn)這些動畫。我們已經(jīng)向你展示了如何在 iOS 中實現(xiàn)這些動畫,所以在 OS X 中實現(xiàn)相似的功能也是非常簡單的。你可以查看在 GitHub 中的完整項目,如果你想要應(yīng)用到 OS X 中,這里還有一些地方需要修改:

  • 第一個要修改的就是 Animator。在Mac中沒有 CADisplayLink,但是取而代之的有 CVDisplayLink,它是以 C 語言為基礎(chǔ)的 API。創(chuàng)建它需要做更多的工作,但也是很直接。
  • iOS 中的彈簧動畫是基于調(diào)整 view 的中心位置來實現(xiàn)的。而 OS X 中的 NSView 類沒有 center 這個屬性,所以我們用為 frame 中的 origin 做動畫來代替。
  • 在 Mac 中是沒有手勢識別,所以我要在我們自定義的 view 子類中實現(xiàn) mouseDown:mouseUp:mouseDragged: 方法。

上面就是我們需要在 Mac 中使用我們的動畫效果在代碼所需要做的修改。對于像這樣的簡單 view,它能很好的勝任。但對于更復(fù)雜的動畫,你可能就不會想通過為 frame 做動畫來實現(xiàn)了,我們可以用 transform 來代替,瀏覽 Jonathan Willing 寫的關(guān)于 OS X 動畫的博客,你會獲益良多。

Facebook 的 POP 框架

上個星期圍繞著 Facebook 的 POP 框架的討論絡(luò)繹不絕。POP 框架是 Paper 應(yīng)用背后支持的動畫引擎。它的操作非常像我們上面講的驅(qū)動動畫的例子,但是它以非常靈活的方式巧妙地封裝到了一個程序包中。

讓我們動手用 POP 來驅(qū)動我們的動畫吧。因為我們自己的類中已經(jīng)封裝了彈簧動畫,這些改變就非常簡單了。我們所要做的就是初始化一個 POP 動畫來代替我們剛才自己做的動畫,并將下面這段代碼加入到 view 中:

- (void)animatePaneWithInitialVelocity:(CGPoint)initialVelocity
{
    [self.pane pop_removeAllAnimations];
    POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
    animation.velocity = [NSValue valueWithCGPoint:initialVelocity];
    animation.toValue = [NSValue valueWithCGPoint:self.targetPoint];
    animation.springSpeed = 15;
    animation.springBounciness = 6;
    [self.pane pop_addAnimation:animation forKey:@"animation"];
    self.animation = animation;
}

你可以在 GitHub 中找到使用 POP 框架的完整例子。

讓其工作非常簡單,并且通過它我們可以實現(xiàn)很多更復(fù)雜的動畫。但是它真正強大的地方在于它能夠?qū)崿F(xiàn)真正的可交互和可中斷的動畫,就像我們上面提到的那樣,因為它直接支持以速度作為輸入?yún)?shù)。如果你打算從一開始到被中斷這過程中的任何時候都能交互,像 POP 這樣的框架就能幫你實現(xiàn)這些動畫,并且它能始終保證動畫一直很平滑。

如果你不滿足于用 POPSpringAnimationPOPDecayAnimation 的開箱即用的處理方式的話,POP 還提供了 POPCustomAnimation 類,它基本上是一個 display link 的方便的轉(zhuǎn)換,來在動畫的每一個 tick 的回調(diào) block 中驅(qū)動你自己的動畫。

展望未來

隨著 iOS7 中從對擬物化的視覺效果的遠離,以及對 UI 行為的關(guān)注,真實的交互式動畫通向未來的大道變得越來越明顯。它們也是將初代 iPhone 中滑動行為的魔力延續(xù)到交互的各個方面的一條康莊大道。為了讓這些魔力成為現(xiàn)實,我們就不能在開發(fā)過程中才想到這些動畫,而是應(yīng)該在設(shè)計時就要考慮這些交互,這一點非常重要。

非常感謝 Loren Brichter 給這篇文章提出的一些意見。