使用 UIKit 和 UIView 在視圖上執行 iOS 動畫
已發表: 2022-03-10十多年來,我一直是一名 iOS 開發人員,很少看到文章整合了在 iOS 中執行動畫的所有可能方式。 本文旨在成為 iOS 動畫的入門讀物,旨在詳盡地介紹實現相同效果的不同方式。
鑑於該主題的廣泛性,我們將在相當高的水平上簡潔地介紹每個部分。 目標是通過一組選擇來教育讀者將動畫添加到他/她的 iOS 應用程序。
在開始討論與 iOS 相關的主題之前,讓我們簡要了解一下動畫速度。
60FPS 動畫
通常在視頻中,每一幀都由一個圖像表示,幀速率決定了序列中翻轉的圖像數量。 這被稱為“每秒幀數”或 FPS。
FPS決定了一秒內翻轉的靜止圖像的數量,字面意思是圖像/幀的數量越多,視頻中顯示的細節/信息就越多。 這也適用於動畫。
FPS 通常用於確定動畫的質量。 有一種流行的觀點認為,任何好的動畫都應該以 60fps 或更高的速度運行——任何低於 60fps 的動畫都會讓人覺得有點過時。
你想看看 30FPS 和 60FPS 的區別嗎? 檢查這個!
你注意到區別了嗎? 人眼絕對可以在較低的 fps 下感覺到抖動。 因此,確保您創建的任何動畫都遵循以 60 FPS 或更高速度運行的基本規則始終是一個好習慣。 這使它感覺更加真實和生動。
看過 FPS 之後,現在讓我們深入研究不同的核心 iOS 框架,它們為我們提供了一種執行動畫的方式。
核心框架
在本節中,我們將介紹 iOS SDK 中可用於創建視圖動畫的框架。 我們將快速瀏覽它們中的每一個,並通過相關示例解釋它們的功能集。
UIKit/ UIView 動畫
UIView 是在 iOS 應用程序中顯示內容的任何視圖的基類。
UIKit,這個給我們提供 UIView 的框架,已經為我們提供了一些基本的動畫功能,方便開發者事半功倍。
API UIView.animate
是動畫視圖的最簡單方法,因為任何視圖的屬性都可以通過在基於塊的語法中提供屬性值來輕鬆動畫。
在 UIKit 動畫中,建議只修改 UIVIew 的動畫屬性,否則動畫可能會導致視圖最終處於意外狀態。
動畫(withDuration:動畫:完成)
這個方法接受動畫持續時間,一組需要動畫的視圖的動畫屬性變化。 當視圖完成執行動畫時,完成塊會給出回調。
幾乎任何類型的動畫,如視圖上的移動、縮放、旋轉、褪色等,都可以使用這個單一的 API 來實現。
現在,考慮您想要為按鈕大小更改設置動畫,或者您想要將特定視圖放大到屏幕中。 這就是我們使用UIView.animate
API 的方法:
let newButtonWidth: CGFloat = 60 UIView.animate(withDuration: 2.0) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) //2 self.button.center = self.view.center //3 }
這是我們在這裡所做的:
- 我們調用
UIView.animate
方法,傳遞給它的持續時間值表示塊內描述的動畫應該運行多長時間。 - 我們設置了應該代表動畫最終狀態的按鈕的新幀。
- 我們將按鈕
center
設置為其超級視圖的中心,以便它保持在屏幕的中心。
上面的動畫代碼塊應該觸發按鈕幀從當前幀改變的動畫:
Width = 0, Height = 0
到最後一幀:
Width = Height = newButtonWidth
這是動畫的樣子:
animateWithDuration
此方法類似於 animate 方法的擴展,您可以在其中執行您可以在先前的 API 中執行的所有操作,並將一些物理行為添加到視圖動畫中。
例如,如果您想在我們上面所做的動畫中實現彈簧阻尼效果,那麼代碼如下所示:
let newButtonWidth: CGFloat = 60 UIView.animate(withDuration: 1.0, //1 delay: 0.0, //2 usingSpringWithDamping: 0.3, //3 initialSpringVelocity: 1, //4 options: UIView.AnimationOptions.curveEaseInOut, //5 animations: ({ //6 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center }), completion: nil)
這是我們使用的一組參數:
-
duration
表示動畫的持續時間,確定代碼塊應該運行多長時間。 -
delay
表示我們希望在動畫開始之前具有的初始延遲。 -
SpringWithDamping
表示我們希望視圖表現的彈性效果的值。 該值必須在 0 到 1 之間。該值越低,彈簧振動越高。 -
velocity
表示動畫應該開始的速度。 -
options
要應用於視圖動畫的動畫曲線類型。 - 最後,我們設置需要動畫的按鈕框架的代碼塊。 和之前的動畫一樣。
以下是使用上述動畫配置的動畫效果:
UIViewPropertyAnimator
為了更好地控制動畫, UIViewPropertyAnimator
很方便,它為我們提供了一種暫停和恢復動畫的方法。 你可以有自定義的時間,讓你的動畫是交互式的和可中斷的。 這在執行也可與用戶操作交互的動畫時非常有用。
經典的“滑動解鎖”手勢和播放器視圖關閉/展開動畫(在音樂應用程序中)是交互式和可中斷動畫的示例。 您可以用手指開始移動視圖,然後鬆開它,視圖將回到原來的位置。 或者,您可以在動畫期間捕捉視圖並繼續用手指拖動它。
以下是我們如何使用UIViewPropertyAnimator
實現動畫的簡單示例:
let newButtonWidth: CGFloat = 60 let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center } animator.startAnimation() //2
這是我們正在做的事情:
- 我們通過傳遞持續時間和動畫曲線來調用
UIViewProperty
API。 - 與上述兩個 UIView.animate API 不同,動畫不會啟動,除非您自己指定它,即您完全控製完整的動畫過程/流程。
現在,假設您想要對動畫進行更多控制。 例如,您想設計和控制動畫中的每一幀。 還有另一個 API, animateKeyframes
。 但在深入研究之前,讓我們快速了解一下動畫中的幀是什麼。
什麼是frame
?
視圖的幀更改/轉換的集合,從開始狀態到最終狀態,被定義為animation
,並且在動畫期間視圖的每個位置被稱為frame
。
動畫關鍵幀
此 API 提供了一種設計動畫的方法,您可以定義具有不同時間和過渡的多個動畫。 發布此消息後,API 將所有動畫簡單地集成到一種無縫體驗中。
假設我們想以隨機方式移動屏幕上的按鈕。 讓我們看看如何使用關鍵幀動畫 API 來做到這一點。
UIView.animateKeyframes(withDuration: 5, //1 delay: 0, //2 options: .calculationModeLinear, //3 animations: { //4 UIView.addKeyframe( //5 withRelativeStartTime: 0.25, //6 relativeDuration: 0.25) { //7 self.button.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.maxY) //8 } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25) { self.button.center = CGPoint(x: self.view.bounds.width, y: start.y) } UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) { self.button.center = start } })
這是細分:
-
duration
通過傳入動畫的持續時間來調用 API。 -
delay
動畫的初始延遲持續時間。 -
options
要應用於視圖動畫的動畫曲線類型。 -
animations
採用開發人員/用戶設計的所有關鍵幀動畫的塊。 -
addKeyFrame
調用 API 來設計每一個動畫。 在我們的例子中,我們已經定義了按鈕的每一次移動。 我們可以根據需要添加盡可能多的此類動畫,並將其添加到塊中。 -
relativeStartTime
定義動畫塊集合中動畫的開始時間。 -
relativeDuration
定義此特定動畫的總持續時間。 -
center
在我們的例子中,我們只需更改按鈕的 center 屬性以在屏幕上移動按鈕。
這就是最終動畫的樣子:
核心動畫
任何基於 UIKit 的動畫都會在內部轉換為核心動畫。 因此,Core Animation 框架充當任何 UIKit 動畫的支持層或骨幹。 因此,所有 UIKit 動畫 API 都只是以易於使用或方便的方式封裝了核心動畫 API 的層。
UIKit 動畫 API 並沒有提供對在視圖上執行的動畫的太多控制,因為它們主要用於視圖的動畫屬性。 因此,在這種情況下,如果您打算控制動畫的每一幀,最好直接使用底層的核心動畫 API。 或者,UIView 動畫和核心動畫也可以結合使用。
UIView + 核心動畫
讓我們看看如何使用 UIView 和 Core Animation API 重新創建相同的按鈕更改動畫以及指定時序曲線。
我們可以使用CATransaction
的計時函數,它可以讓您指定和控制動畫曲線。
讓我們看一個使用CATransaction
的計時功能和 UIView 動畫組合的按鈕大小更改動畫的示例:
let oldValue = button.frame.width/2 let newButtonWidth: CGFloat = 60 /* Do Animations */ CATransaction.begin() //1 CATransaction.setAnimationDuration(2.0) //2 CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) //3 // View animations //4 UIView.animate(withDuration: 1.0) { self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center } // Layer animations let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius)) //5 cornerAnimation.fromValue = oldValue //6 cornerAnimation.toValue = newButtonWidth/2 //7 button.layer.cornerRadius = newButtonWidth/2 //8 button.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius)) //9 CATransaction.commit() //10
這是細分:
-
begin
表示動畫代碼塊的開始。 -
duration
整體動畫持續時間。 -
curve
表示需要應用於動畫的時序曲線。 -
UIView.animate
我們的第一個動畫改變了按鈕的框架。 -
CABasicAnimation
我們通過將按鈕的角cornerRadius
作為鍵路徑來創建CABasicAnimation
對象,因為這就是我們想要動畫的對象。 同樣,如果您想對關鍵幀動畫進行精細級別控制,則可以使用CAKeyframeAnimation
類。 -
fromValue
表示動畫的起始值,即動畫必須從其開始的按鈕的初始cornerRadius
值。 -
toValue
表示動畫的最終值,即動畫必須結束的按鈕的最終cornerRadius
值。 -
cornerRadius
我們必須將按鈕的cornerRadius
屬性設置為動畫的最終值,否則按鈕的角半徑值將在動畫完成後自動恢復為其初始值。 -
addAnimation
我們通過表示需要執行動畫的 Keypath 將包含整個動畫過程配置的動畫對象附加到圖層。 -
commit
表示動畫代碼塊的結束並開始動畫。
這是最終動畫的樣子:
這篇博客非常適合幫助創建更高級的動畫,因為它巧妙地引導您完成大多數 Core Animation 框架 API,並提供指導您完成每一步的說明。
UIKitDynamics
UIKit Dynamics 是 UIKit 的物理引擎,它使您能夠將任何物理行為(如碰撞、重力、推動、捕捉等)添加到 UIKit 控件。
UIKitDynamicAnimator
這是 UIKit Dynamics 框架的管理類,它管理由任何給定 UI 控件觸發的所有動畫。
UIKit動態行為
它使您能夠將任何物理行為添加到動畫師,然後使其能夠在附加到它的視圖上執行。
UIKitDynamics 的不同類型的行為包括:
-
UIAttachmentBehavior
-
UICollisionBehavior
-
UIFieldBehavior
-
UIGravityBehavior
-
UIPushBehavior
-
UISnapBehavior
UIKitDynamics 的架構看起來像這樣。 請注意,項目 1 到 5 可以替換為單個視圖。
讓我們對按鈕應用一些物理行為。 我們將看到如何將重力應用到按鈕上,以便它給我們一種處理真實對象的感覺。
var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3
這是細分:
-
UIKitDynamicAnimator
我們創建了一個UIKitDynamicAnimator
對象,它充當執行動畫的協調器。 我們還通過了按鈕的 superview 作為參考視圖。 -
UIGravityBehavior
我們創建了一個UIGravityBehavior
對象,並將我們的按鈕傳遞到注入此行為的數組元素中。 -
addBehavior
我們已將重力對象添加到動畫師。
這應該創建一個動畫,如下所示:
我們應該告訴動畫師將屏幕底部視為地面。 這就是UICollisionBehavior
作用的地方。var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! var collisionBehavior : UICollisionBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4 collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5 dynamicAnimator.addBehavior(collisionBehavior) //6
-
UICollisionBehavior
我們創建了一個UICollisionBehavior
對象並傳遞按鈕,以便將行為添加到元素中。 -
translatesReferenceBoundsIntoBoundary
啟用此屬性會告訴動畫師將參考視圖邊界作為結束,在我們的例子中是屏幕的底部。 -
addBehavior
我們在這里為動畫師添加了碰撞行為。
現在,我們的按鈕應該落地並保持靜止,如下所示:
這很整潔,不是嗎?
現在,讓我們嘗試添加一個彈跳效果,讓我們的對象感覺更真實。 為此,我們將使用UIDynamicItemBehavior
類。var dynamicAnimator : UIDynamicAnimator! var gravityBehavior : UIGravityBehavior! var collisionBehavior : UICollisionBehavior! var bouncingBehavior : UIDynamicItemBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2 dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4 collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5 dynamicAnimator.addBehavior(collisionBehavior) //6 //Adding the bounce effect bouncingBehavior = UIDynamicItemBehavior(items: [button]) //7 bouncingBehavior.elasticity = 0.75 //8 dynamicAnimator.addBehavior(bouncingBehavior) //9
-
UIDynamicItemBehavior
我們創建了一個UIDynamicItemBehavior
對象並傳遞按鈕,以便將行為添加到元素中。 -
elasticity
值必須在 0-1 之間,它代表彈性,即物體在被擊中時必須在地面上反彈和離開地面的次數。 這就是魔法發生的地方——通過調整這個屬性,你可以區分不同種類的物體,比如球、瓶子、硬物等等。 -
addBehavior
我們在這里為動畫師添加了碰撞行為。
現在,我們的按鈕在落地時應該會彈跳,如下所示:
這個 repo 很有幫助,展示了 UIKitDynamics 的所有行為。 它還提供了用於處理每種行為的源代碼。 在我看來,這應該作為在視圖上執行 iOS 動畫的方法的廣泛列表!
在下一節中,我們將簡要介紹有助於我們測量動畫性能的工具。 我還建議您考慮優化 Xcode 構建的方法,因為它可以節省大量的開發時間。
性能調優
在本節中,我們將研究測量和調整 iOS 動畫性能的方法。 作為一名 iOS 開發人員,您可能已經使用過諸如 Memory Leaks 和 Allocations 之類的 Xcode Instruments 來衡量整個應用程序的性能。 同樣,也有一些工具可以用來衡量動畫的表現。
Core Animation
儀器
嘗試使用Core Animation
工具,您應該能夠看到您的應用屏幕提供的 FPS。 這是衡量 iOS 應用程序中渲染的任何動畫的性能/速度的好方法。
繪畫
在顯示大量內容(如帶有陰影效果的圖像)的應用程序中,FPS 大大降低。 在這種情況下,不要將 Image 直接分配給UIImageView
的 image 屬性,而是嘗試使用 Core Graphics API 在上下文中單獨繪製圖像。 當在單獨的線程而不是主線程中完成時,通過異步執行圖像解壓縮邏輯,這過度減少了圖像顯示時間。
光柵化
光柵化是一個用於緩存複雜圖層信息的過程,以便這些視圖在渲染時不會重繪。 重繪視圖是降低 FPS 的主要原因,因此,最好對將要重複使用多次的視圖應用光柵化。
包起來
最後,我還總結了 iOS 動畫的有用資源列表。 在處理 iOS 動畫時,您可能會發現這非常方便。 此外,您還可能會發現這套設計工具在深入研究動畫之前作為(設計)步驟很有幫助。
我希望我能夠涵蓋盡可能多的圍繞 iOS 動畫的主題。 如果本文中我可能遺漏了什麼,請在下面的評論部分告訴我,我很樂意補充!