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

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

動畫解釋

我們寫的應用程序往往都不是靜態(tài)的,因為它們需要適應用戶的需求以及為執(zhí)行各種任務而改變狀態(tài)。

在這些狀態(tài)之間轉(zhuǎn)換時,清晰的揭示正在發(fā)生什么是非常重要的。而不是在頁面之間跳躍,動畫幫助我們解釋用戶從哪里來,要到哪里去。

鍵盤在 view 中滑進滑出給了我們一個錯覺,讓我們以為它是簡單的被隱藏在屏幕下方的,并且是手機很自然的一個部分。View controller 轉(zhuǎn)場加強了我們的應用程序的導航結構,并且給了用戶正在移向哪個方向的提示。微妙的反彈和碰撞使界面栩栩如生,并且激發(fā)出了物理的質(zhì)感。要是沒有這些的話,我們就只有一個沒有視覺修飾的干巴巴環(huán)境了。

動畫是敘述你的應用的故事的絕佳方式,在了解動畫背后的基本原理之后,設計它們會輕松很多。

首要任務

在這篇文章 (以及這個話題中其余大多數(shù)文章) 中,我們將特別地針對 Core Animation 進行探討。雖然你將看到的很多東西也可以用更高層級的 UIKit 的方法來完成,但是 Core Animation 將會讓你更好的理解正在發(fā)生什么。它以一種更明確的方式來描述動畫,這對這篇文章讀者以及你自己的代碼的讀者來說都非常有用。

在看動畫如何與我們在屏幕上看到的內(nèi)容交互之前,我們需要快速瀏覽一下 Core Animation 的 CALayer,這是動畫產(chǎn)生作用的地方。

你大概知道 UIView 實例,以及 layer-backed 的 NSView,修改它們的 layer 來委托強大的 Core Graphics 框架來進行渲染。然而,你務必要理解,當把動畫添加到一個 layer 時,是不直接修改它的屬性的。

取而代之,Core Animation 維護了兩個平行 layer 層次結構: model layer tree(模型層樹)presentation layer tree(表示層樹)。前者中的 layers 反映了我們能直接看到的 layers 的狀態(tài),而后者的 layers 則是動畫正在表現(xiàn)的值的近似。

實際上還有所謂的第三個 layer 樹,叫做 rendering tree(渲染樹)。因為它對 Core Animation 而言是私有的,所以我們在這里不討論它。

考慮在 view 上增加一個漸出動畫。如果在動畫中的任意時刻,查看 layer 的 opacity 值,你是得不到與屏幕內(nèi)容對應的透明度的。取而代之,你需要查看 presentation layer 以獲得正確的結果。

雖然你可能不會去直接設置 presentation layer 的屬性,但是使用它的當前值來創(chuàng)建新的動畫或者在動畫發(fā)生時與 layers 交互是非常有用的。

通過使用 -[CALayer presentationLayer]-[CALayer modelLayer],你可以在兩個 layer 之間輕松切換。

基本動畫

可能最常見的情況是將一個 view 的屬性從一個值改變?yōu)榱硪粋€值,考慮下面這個例子:

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

在這里,我們讓紅色小火箭的 x-position 從 77.0 變?yōu)?455.0,剛好超過它的 parent view 的邊。為了填充所有路徑,我們需要確定我們的火箭在任意時刻所到達的位置。這通常使用線性插值法來完成:

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

也就是說,對于動畫給定的一個分數(shù) t,火箭的 x 坐標就是起始點的 x 坐標 77,加上一個到終點的距離 ?x = 378 乘以該分數(shù)的值。

使用 CABasicAnimation,我們可以如下實現(xiàn)這個動畫:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

[rocket.layer addAnimation:animation forKey:@"basic"];

請注意我們要動畫的鍵路徑,也就是 position.x,實際上包含一個存儲在 position 屬性中的 CGPoint 結構體成員。這是 Core Animation 一個非常方便的特性。請務必查看支持的鍵路徑的完整列表。

然而,當我們運行該代碼時,我們意識到火箭在完成動畫后馬上回到了初始位置。這是因為在默認情況下,動畫不會在超出其持續(xù)時間后還修改 presentation layer。實際上,在結束時它甚至會被徹底移除。

一旦動畫被移除,presentation layer 將回到 model layer 的值,并且因為我們從未修改該 layer 的 position 屬性,所以我們的飛船將重新出現(xiàn)在它開始的地方。

這里有兩種解決這個問題的方法:

第一種方法是直接在 model layer 上更新屬性。這是推薦的的做法,因為它使得動畫完全可選。

一旦動畫完成并且從 layer 中移除,presentation layer 將回到 model layer 設置的值,而這個值恰好與動畫最后一個步驟相匹配。

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

[rocket.layer addAnimation:animation forKey:@"basic"];

rocket.layer.position = CGPointMake(455, 61);

或者,你可以通過設置動畫的 fillMode 屬性為 kCAFillModeForward 以留在最終狀態(tài),并設置removedOnCompletionNO 以防止它被自動移除:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

animation.fillMode = kCAFillModeForward;
animation.removedOnCompletion = NO;

[rectangle.layer addAnimation:animation forKey:@"basic"];

Andy Matuschak 指出了,如果將已完成的動畫保持在 layer 上時,會造成額外的開銷,因為渲染器會去進行額外的繪畫工作。

值得指出的是,實際上我們創(chuàng)建的動畫對象在被添加到 layer 時立刻就復制了一份。這個特性在多個 view 中重用動畫時這非常有用。比方說我們想要第二個火箭在第一個火箭起飛不久后起飛:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.byValue = @378;
animation.duration = 1;

[rocket1.layer addAnimation:animation forKey:@"basic"];
rocket1.layer.position = CGPointMake(455, 61);

animation.beginTime = CACurrentMediaTime() + 0.5;

[rocket2.layer addAnimation:animation forKey:@"basic"];
rocket2.layer.position = CGPointMake(455, 111);

設置動畫的 beginTime 為未來 0.5 秒將只會影響 rocket2,因為動畫在執(zhí)行語句 [rocket1.layer addAnimation:animation forKey:@"basic"]; 時已經(jīng)被復制了,并且之后 rocket1 也不會考慮對動畫對象的改變。

不妨看一看 David 的 關于動畫時間的一篇很棒的文章,通過它可以學習如何更精確的控制你的動畫。

我決定再使用 CABasicAnimationbyValue 屬性創(chuàng)建一個動畫,這個動畫從 presentation layer 的當前值開始,加上 byValue 的值后結束。這使得動畫更易于重用,因為你不需要精確的指定可能無法提前知道的 from-toValue 的值。

fromValue, byValuetoValue 的不同組合可以用來實現(xiàn)不同的效果,如果你需要創(chuàng)建一個可以在你的不同應用中重用的動畫,你可以查看文檔

多步動畫

這很容易想到一個場景,你想要為你的動畫定義超過兩個步驟,我們可以使用更通用的 CAKeyframeAnimation,而不是去鏈接多個 CABasicAnimation 實例。

關鍵幀(keyframe)使我們能夠定義動畫中任意的一個點,然后讓 Core Animation 填充所謂的中間幀。

比方說我們正在制作我們下一個 iPhone 應用程序上的登陸表單,我們希望當用戶輸入錯誤的密碼時表單會晃動。使用關鍵幀動畫,看起來大概像下面這樣:

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

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"];

values 數(shù)組定義了表單應該到哪些位置。

設置 keyTimes 屬性讓我們能夠指定關鍵幀動畫發(fā)生的時間。它們被指定為關鍵幀動畫總持續(xù)時間的一個分數(shù)。

請注意我是如何選擇不同的值從 0 到 10 和從 10 到 -10 轉(zhuǎn)換以維持恒定的速度的。

設置 additive 屬性為 YES 使 Core Animation 在更新 presentation layer 之前將動畫的值添加到 model layer 中去。這使我們能夠?qū)λ行问降男枰碌脑刂赜孟嗤膭赢嫞覠o需提前知道它們的位置。因為這個屬性從 CAPropertyAnimation 繼承,所以你也可以在使用 CABasicAnimation 時使用它。

沿路徑的動畫

雖然用代碼實現(xiàn)一個簡單的水平晃動并不難,但是沿著復雜路徑的動畫就需要我們在關鍵幀的 values 數(shù)組中存儲大量 box 化的 CGPoint。 值得慶幸的是,CAKeyframeAnimation 提供了更加便利的 path 屬性作為代替。

舉個例子,我們?nèi)绾巫屢粋€ view 做圓周運動:

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

CGRect boundingRect = CGRectMake(-150, -150, 300, 300);

CAKeyframeAnimation *orbit = [CAKeyframeAnimation animation];
orbit.keyPath = @"position";
orbit.path = CFAutorelease(CGPathCreateWithEllipseInRect(boundingRect, NULL));
orbit.duration = 4;
orbit.additive = YES;
orbit.repeatCount = HUGE_VALF;
orbit.calculationMode = kCAAnimationPaced;
orbit.rotationMode = kCAAnimationRotateAuto;

[satellite.layer addAnimation:orbit forKey:@"orbit"];

使用 CGPathCreateWithEllipseInRect(),我們創(chuàng)建一個圓形的 CGPath 作為我們的關鍵幀動畫的 path。

使用 calculationMode 是控制關鍵幀動畫時間的另一種方法。我們通過將其設置為 kCAAnimationPaced,讓 Core Animation 向被驅(qū)動的對象施加一個恒定速度,不管路徑的各個線段有多長。將其設置為 kCAAnimationPaced 將無視所有我們已經(jīng)設置的 keyTimes。

設置 rotationMode 屬性為 kCAAnimationRotateAuto 確保飛船沿著路徑旋轉(zhuǎn)。作為對比,如果我們將該屬性設置為 nil 那動畫會是什么樣的呢。

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

你可以使用帶路徑的動畫來實現(xiàn)幾個有趣的效果;資深 objc.io 作者 Ole Begemann 寫了一篇文章,闡述了如何將 CAShapeLayer 與基于路徑的動畫組合起來使用,并只用幾行代碼來創(chuàng)建酷炫的繪圖動畫。

時間函數(shù)

讓我們再次來看看第一個例子:

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

你會發(fā)現(xiàn)我們的火箭的動畫有一些看起來非常不自然的地方。那是因為我們在現(xiàn)實世界中看到的大部分運動需要時間來加速或減速。對象瞬間達到最高速度,然后再立即停止往往看起來非常不自然。除非你在讓機器人跳舞,但這很少是想要的效果。

為了給我們的動畫一個存在慣性的感覺,我們可以使用我們上面提到的參數(shù)因子來進行插值。然而,如果我們接下來需要為每個需要加速或減速的行為創(chuàng)建一個新的插值函數(shù),這將是一個很難擴展的方法。

取而代之,常見的做法是把要進行動畫的屬性的插值從動畫的速度中解耦出來。這樣一來,給動畫提速會產(chǎn)生一種小火箭加速運動的效果,而不用改變我們的插值函數(shù)。

我們可以通過引入一個 時間函數(shù) (timing function) (有時也被稱為 easing 函數(shù))來實現(xiàn)這個目標。該函數(shù)通過修改持續(xù)時間的分數(shù)來控制動畫的速度。

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

最簡單的 easing 函數(shù)是 linear。它在整個動畫上維持一個恒定的速度。在 Core Animation 中,這個功能由 CAMediaTimingFunction 類表示。

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

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x"; 
animation.fromValue = @50;
animation.toValue = @150;
animation.duration = 1;

animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

[rectangle.layer addAnimation:animation forKey:@"basic"];

rectangle.layer.position = CGPointMake(150, 0);

Core Animation 附帶了一些 linear 之外的內(nèi)置 easing 函數(shù),如:

  • Ease in (kCAMediaTimingFunctionEaseIn):
    http://wiki.jikexueyuan.com/project/objc/images/12-6.gif" alt="" />
  • Ease out (kCAMediaTimingFunctionEaseOut):
    http://wiki.jikexueyuan.com/project/objc/images/12-7.gif" alt="" />
  • Ease in ease out (kCAMediaTimingFunctionEaseInEaseOut):
    http://wiki.jikexueyuan.com/project/objc/images/12-8.gif" alt="" />
  • 默認 (kCAMediaTimingFunctionDefault):
    http://wiki.jikexueyuan.com/project/objc/images/12-9.gif" alt="" />

在一定限度內(nèi),你也可以使用 +functionWithControlPoints:::: 創(chuàng)建自己的 easing 函數(shù)。通過傳遞 cubic Bézier 曲線的兩個控制點的 xy 坐標,你可以輕松的創(chuàng)建自定義 easing 函數(shù),比如我為我們的紅色小火箭選擇的那個。

這個方法因為有三個無名參數(shù)而聲名狼藉,我們并不推薦在你的 API 中使用這種蛋疼的寫法。

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

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.5:0:0.9:0.7];

[rocket.layer addAnimation:animation forKey:@"basic"];

rocket.layer.position = CGPointMake(150, 0);

我不打算講太多關于 Bézier 曲線的細節(jié),在計算機圖形學中,它們是創(chuàng)建平滑曲線的常用技術。你可能在基于矢量的繪圖工具,比如 Sketch 或 Adobe Illustrator 中見過它們。

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

傳遞給 +functionWithControlPoints:::: 的值有效地控制了控制點的位置。所得到的定時函數(shù)將基于得到的路徑來調(diào)整動畫的速度。x 軸代表時間的分數(shù),而 y 軸是插值函數(shù)的輸入值。

遺憾的是,由于這些部分被鎖定在 [0–1] 的范圍內(nèi),我們不可能用它來創(chuàng)建一些像預期動作 (Anticipation,一種像目標進發(fā)前先回退一點,到達目標后還過沖一會兒,見下圖) 這樣的常見效果。

我寫了一個小型庫,叫做 RBBAnimation,它包含一個允許使用 更多復雜 easing 函數(shù) 的自定義子類 CAKeyframeAnimation,包括反彈和包含負分量的 cubic Bézier 函數(shù):

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

RBBTweenAnimation *animation = [RBBTweenAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @50;
animation.toValue = @150;
animation.duration = 1;

animation.easing = RBBCubicBezier(0.68, -0.55, 0.735, 1.55);

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

RBBTweenAnimation *animation = [RBBTweenAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @50;
animation.toValue = @150;
animation.duration = 1;

animation.easing = RBBEasingFunctionEaseOutBounce;

動畫組

對于某些復雜的效果,可能需要同時為多個屬性進行動畫。想象一下,在一個媒體播放程序中,當切換到到隨機曲目時我們讓隨機動畫生效??雌饋砭拖裣旅孢@樣:

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

你可以看到,我們需要同時對上面的封面的 position,rotation 和 z-position 進行動畫。使用 CAAnimationGroup 來動畫其中一個封面的代碼大概如下:

CABasicAnimation *zPosition = [CABasicAnimation animation];
zPosition.keyPath = @"zPosition";
zPosition.fromValue = @-1;
zPosition.toValue = @1;
zPosition.duration = 1.2;

CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
rotation.keyPath = @"transform.rotation";
rotation.values = @[ @0, @0.14, @0 ];
rotation.duration = 1.2;
rotation.timingFunctions = @[
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
];

CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.keyPath = @"position";
position.values = @[
    [NSValue valueWithCGPoint:CGPointZero],
    [NSValue valueWithCGPoint:CGPointMake(110, -20)],
    [NSValue valueWithCGPoint:CGPointZero]
];
position.timingFunctions = @[
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
];
position.additive = YES;
position.duration = 1.2;

CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = @[ zPosition, rotation, position ];
group.duration = 1.2;
group.beginTime = 0.5;

[card.layer addAnimation:group forKey:@"shuffle"];

card.layer.zPosition = 1;

我們使用 CAAnimationGroup 得到的一個好處是可以將所有動畫作為一個對象暴露出去。如果你要在應用程序中的多個地方用工廠對象創(chuàng)建的重用的動畫的話,這將會非常有用。

你也可以使用動畫組同時控制所有動畫組成部分的時間。

Core Animation 之外

都現(xiàn)在了,你應該已經(jīng)聽說過 UIKit Dynamics 了,這是 iOS 7 中引入的一個物理模擬框架,它允許你使用約束和力來為 views 做動畫。與 Core Animation 不同,它與你在屏幕上看到的內(nèi)容交互更為間接,但是它的動態(tài)特性讓你可以在事先不知道結果時創(chuàng)建動畫。

Facebook 最近開源了 Paper 背后的動畫引擎 Pop。從概念上講,它介于 Core Animation 和 UIKit Dynamics 之間。它完美的使用了彈簧(spring)動畫,并且能夠在動畫運行時操控目標值,而無需替換它。Pop 也可以在 OS X 上使用,并且允許我們在每個 NSObject 的子類中為任意屬性進行動畫。

擴展閱讀

上一篇:相機工作原理下一篇:Build 工具