iOS Animation 指北

a7820832 7年前發布 | 9K 次閱讀 UIKit iOS開發 移動開發 Core Animation

寫在前面

iOS 向來以絲般順滑的過度動畫聞名,好的動畫可以讓用戶更好地理解 app,并且可以讓 app 更加有趣。有趣很重要。

iOS 動畫(或者所有動畫?)的原理簡單來講有兩種:

  1. 告訴系統動畫對象在某幾個時刻的狀態(關鍵幀),由系統自動補全這些時刻之間的中間狀態,再把所有這些狀態平滑地顯示出來。這種動畫也叫做關鍵幀動畫。
  2. 每隔一段很短的時間,重新繪制一次動畫對象。

整體架構

這里是一張 iOS 動畫相關 framework 的架構圖:

從圖中可以看到,要實現一個 iOS 動畫從上層到底層你會接觸到 UIKit、Core Animation、Core Graphics/OpenGL。他們使用的難度依次遞增,相應地靈活程度以及能實現效果的復雜程度也越來越高。

基礎知識

CALayer

CALayer 是 UIView 背后用來管理顯示內容的對象,它維護了一個位圖以及位圖的狀態信息。這個位圖可能是開發者用程序繪制的(比如在 drawRect 中),也可能是開發者指定的一張圖片。

Core Animation 的所有功能都是基于 layer 的。當你通過 Core Animation 修改了 layer 的屬性,Core Animation 會通過 GPU 來重新渲染位圖。

利用 CALayer 的屬性可以做很多事情,比如改變位置、大小、形狀、透視角度、圓角、透明度等,利用 mask 屬性還可以做出很多好玩的效果。這些屬性的詳細介紹在 這里

CALayer 有很多有用的子類,常用的有下面幾個:

  • CAShapeLayer:用于繪制曲線等圖形,有兩個重要的屬性 strokeStart & strokeEnd ,靈活使用有奇效。
  • CATextLayer:專門用來處理文字的 layer。
  • CAGradientLayer:顧名思義,用來處理漸變。

Timing function

Timing function 用來描繪動畫完成度隨時間增加的曲線。

怎么理解這個含義呢,在前面提到的第二種動畫實現方式中,我們設置好了關鍵幀后,系統需要計算出關鍵幀中間各個時間點的狀態。

假設我們要完成的是一個物體沿直線從 (0,0) 移動到 (0, 100) 的動畫,動畫時長是 1s。系統可以以勻速將物體從起點移動到終點,也可以加速或者減速,甚至可以先加速再減速移動到終點,timing function 就是用來描繪這里的加減速。

看看 iOS 預定義的幾種常見的 timing function:

let kCAMediaTimingFunctionLinear: String
let kCAMediaTimingFunctionEaseIn: String
let kCAMediaTimingFunctionEaseOut: String
let kCAMediaTimingFunctionEaseInEaseOut: String
let kCAMediaTimingFunctionDefault: String

可以看到,這里的橫坐標是時間,縱坐標可以看做動畫的完成度。

正如命名描繪的那樣, kCAMediaTimingFunctionLinear 是線性增加的,而 kCAMediaTimingFunctionEaseOut 是先快后慢的。

你甚至還可以通過 init(controlPoints:_:_:_:) 方法,傳入兩個貝塞爾曲線的控制點來自定義想要的曲線。

這樣做的意義是什么呢?因為人們在日常生活中見到的物體的運動幾乎沒有勻速運動的,比如汽車啟動和剎車,比如杯子從桌子上掉落。合理運用 timing function 可以使動畫更符合人們的經驗,因而顯得更加自然。

UIKit

UIKit 提供了一系列的基于 block 的 API。比如 UIView.animate() 系列方法。對于 view 的 animatable properties , 你只需要在 block 中修改需要對應的參數即可,比如 frame、bounds、alpha 等。

如果想實現關鍵幀動畫,UIKit 還提供了 UIView.animateKeyframes() 方法。

UIKit 適用于簡單的動畫,如移動、旋轉、縮放、改變顏色等。

Core Animation

UIKit 實現動畫適用起來非常簡單,但是有很大的局限性:如果我想改變 cornerRadius 怎么辦?如果我想沿一條曲線移動一個 view 怎么辦?

這個時候就需要使用 Core Animation 了(UIKit 其實也是在 Core Animation 的基礎上做了一層封裝)。

Core Animation 分為以下幾個部分:

CABasicAnimation

相比于 UIKit,CABasicAnimation 可以對更多的 CALayer 屬性進行動畫,比如 cornerRadius。完整的列表見 這里

CABasicAnimation 使用起來非常方便:

let verticalAnimation = CABasicAnimation(keyPath: "position.y") // 1
verticalAnimation.fromValue = 310       // 2
verticalAnimation.toValue = 10      // 3
verticalAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)        // 4
view.layer.add(verticalAnimation, forKey: "Fall")   // 5

view.layer.position.y = 10 //6 </code></pre>

簡單解釋一下:

  1. 這里我們創建了一個 CABasicAnimation 實例,要對 layer 的 position.y 屬性進行動畫。
  2. position.y 的初始值是 310。
  3. position.y 的最終值是 10。
  4. 指定了動畫的 timing function,后面會介紹。
  5. 將動畫添加到 view 的 layer 上。
  6. 將 layer 的 model layer 屬性更改為最終屬性。

什么是 model layer?

Core Animation 實際維護了三個 layer:model layer、presentation layer 和 render layer,其中的前兩個我們平時會接觸到,可以分別使用 CALayer 的兩個屬性 modelLayer 和 presentationLayer 來獲得。Render layer 是系統私有的。

Model layer 的屬性是不會變化的,如果你想得到 layer 在動畫過程中實時的屬性,就需要通過 presentation layer 來獲取。

CAKeyframeAnimation

CABasicAnimation 只能讓你設置一個初始狀態和一個結束狀態,如果你的動畫需要拆解成幾個連貫的動作,CAKeyframeAnimation 可以傳入多個不同的值。

此外,CAKeyframeAnimation 還可以設置 CGPath,也就是說你可以讓動畫對象沿著曲線移動。

let positionAnimation = CAKeyframeAnimation(keyPath: "position")
positionAnimation.path = path.cgPath
positionAnimation.isRemovedOnCompletion = false
positionAnimation.fillMode = kCAFillModeForwards

view.layer.add(positionAnimation, forKey: "MoveAlongPath") </code></pre>

CATransition

CATransition 用來進行 view 的轉場動畫,具體類型有以下四種:

let kCATransitionFade: String
let kCATransitionMoveIn: String
let kCATransitionPush: String
let kCATransitionReveal: String

還有一些私有的類型,不推薦使用。

CATransition 是 CAAnimation 的子類,使用方法跟 CABasicAnimation 一致。

CAAnimationGroup

如果我想讓物體在移動的同時由不透明動畫到透明,就可以使用 CAAnimationGroup,把多個動畫組合起來,同時添加到動畫對象上。

let animationA = ...
let animationB = ...
let animationC = ...

let animationGroup = CAAnimationGroup() animationGroup.animations = [animationA, animationB, animationC]; animationGroup.duration = 0.7 animationGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) animationGroup.isRemovedOnCompletion = false animationGroup.fillMode = kCAFillModeForwards

view.layer.add(animationGroup, forKey: "GroupAnimation") </code></pre>

此外,CAAnimationGroup 還可以設置一個 completion block,在所有動畫完成時調用。

Core Animation 適用于較為復雜的,有多個中間狀態或者包含曲線路徑的動畫。

Core Graphics

如果動畫復雜到不能夠用改變位置、透明度、大小等屬性的組合來完成,就需要使用 Core Graphics 了。

Core Graphics 是一套用來繪圖的框架,你可以繪制曲線、填充形狀,做任何想做的事情。這個時候采用的就是前面提到的第二種動畫方法:每隔一段很短的時間,重新繪制一次動畫對象。

注意,采用 Core Graphics 繪制圖形是非常消耗性能的,因為繪制工作由 CPU 完成,而且是在主線程上!如果是簡單的圖形可以使用 CAShapeLayer ,利用 GPU 繪制。

如何來保證「每隔一段很短的時間」呢?iOS 為此提供了 CADisplayLink。它可以被看作是一個特殊的 timer,在系統刷新每一幀的時候,調用開發者設置的回調來重新繪制動畫對象。iOS 屏幕的刷新頻率是 60幀每秒,也就是每隔約 16.7 毫秒調用一次回調。這也意味著,動畫中每一幀的繪制都不應該超過 16.7 毫秒。

那是不是用 NSTimer 也可以達到目的?

并不是的。CADisplayLink 可以保證每次都是在 屏幕刷新的時刻附近 來調用回調——也就是說,你的每一幀都有約 16.7 毫秒來繪制。NSTimer 不能保證觸發時刻都落在屏幕刷新的時刻附近,有可能你的一幀只有 2 毫秒來繪制。

Core Graphics + CADisplayLink 適用于復雜的,不能使用移動、旋轉、形變組合完成的動畫。

大殺器 UIKit Dynamics

UIKit Dynamics 是隨 iOS 7 推出的一套 framework,作用是模擬真實事件的物理定律。蘋果聲稱你可以「聲明式」地編寫動畫,你只需要描述要做什么,而不必說明怎么做,一切都由系統幫你完成。

Ref

 

來自:http://nixwang.com/2017/02/13/ios-animation/

 

 本文由用戶 a7820832 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!