iOS 動畫主要是指
Core Animation
框架,Core Animation
是iOS
和OS X
平臺上負責圖形渲染與動畫的基礎框架。Core Animation
可以作用于動畫視圖或者其他可視元素,可以完成動畫所需的大部分繪幀工作。Core Animation
系統已經進行了封裝, 所以在使用的時候你只需要配置少量的動畫參數(如開始點的位置和結束點的位置)即可使用Core Animation
的多種動畫效果。Core Animation
將大部分實際的繪圖任務交給了圖形硬件(GPU)來處理,圖形硬件會加速圖形渲染的速度。這種自動化的圖形加速技術讓動畫擁有更高的幀率并且顯示效果更加平滑,不會加重CPU的負擔而影響程序的運行速度。
本文主要總結下平時常用的動畫, 如: 基礎動畫(CABasicAnimation
)、關鍵幀動畫(CAKeyframeAnimation
)、組動畫(CAAnimationGroup
)、過渡動畫(CATransition
), 最后也擴展了下, 做了進度條、貝塞爾曲線畫心??、彈球、釘釘效果、點贊等動畫,希望對大家有所幫助.
github: https://github.com/YTiOSer/YTAnimation
一、Core Animation類簡介
-
首先通過官方的
Core Animation
類圖了解下各個類之間的關系. 官網鏈接:Core Animation
Core Animation.png
建議詳細看下上圖, 這里對CAAnimation
的子類和相互關系及屬性介紹的比較詳細, 看完后會對各個動畫類型有個大概的了解. 接下來詳細介紹下動畫的各個屬性及作用
- fromValue: 動畫的開始值(Any類型, 根據動畫不同可以是
CGPoint
、NSNumber等) - toValue: 動畫的結束值, 和fromValue類似
- beginTime: 動畫的開始時間
- duration : 動畫的持續時間
- repeatCount : 動畫的重復次數
- fillMode: 動畫的運行場景
- isRemovedOnCompletion: 完成后是否刪除動畫
- autoreverses: 執行的動畫按照原動畫返回執行
- path:關鍵幀動畫中的執行路徑
- values: 關鍵幀動畫中的關鍵點數組
- animations: 組動畫中的動畫數組
- delegate : 動畫代理, 封裝了動畫的執行和結束方法
- timingFunction: 控制動畫的顯示節奏, 系統提供五種值選擇,分別是:
1.kCAMediaTimingFunctionDefault
( 默認,中間快)
2.kCAMediaTimingFunctionLinear
(線性動畫)
3.kCAMediaTimingFunctionEaseIn
(先慢后快 慢進快出)
4.kCAMediaTimingFunctionEaseOut
(先塊后慢快進慢出)
5.kCAMediaTimingFunctionEaseInEaseOut
(先慢后快再慢) - type: 過渡動畫的動畫類型,系統提供了多種過渡動畫, 分別是:
1:fade
(淡出 默認)
2:moveIn
(覆蓋原圖)
3:push
(推出)
4:fade
(淡出 默認)
5:reveal
(底部顯示出來)
6:cube
(立方旋轉)
7:suck
(吸走)
8:oglFlip
(水平翻轉 沿y軸)
9:ripple
(滴水效果)
10:curl
(卷曲翻頁 向上翻頁)
11:unCurl
(卷曲翻頁返回 向下翻頁)
12:caOpen
(相機開啟)
13:caClose
(相機關閉) - subtype : 過渡動畫的動畫方向, 系統提供了四種,分別是:
1.fromLeft
( 從左側)
2.fromRight
(從右側)
3.fromTop
(有上面)
4.fromBottom
(從下面)
二、Core Animation的使用
1. 基礎動畫( CABasicAnimation
)
基礎動畫主要提供了對于CALayer對象中的可變屬性進行簡單動畫的操作。比如:位移、旋轉、縮放、透明度、背景色等。
基礎動畫根據keyPath
來區分不同的動畫,, 系統提供了多個類型,如:transform.scale
(比例轉換)、transform.scale.x
、transform.scale.y
、transform.rotation
(旋轉) 、transform.rotation.x
(繞x軸旋轉)、transform.rotation.y
(繞y軸旋轉)、transform.rotation.z
(繞z軸旋轉)、opacity
(透明度)、margin
、backgroundColor
(背景色)、cornerRadius
(圓角)、borderWidth
(邊框寬)、bounds
、contents
、contentsRect
、cornerRadius
、frame
、hidden
、mask
、masksToBounds
、shadowColor
(陰影色)、shadowOffset
、shadowOpacity
、shadowOpacity
, 在使用時候, 需要根據具體的需求選擇合適的.
效果圖如下:
- 位移動畫
func positionAnimation() {
let animation = CABasicAnimation.init(keyPath: "position") //keyPath為系統提供
animation.fromValue = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_Top)
animation.toValue = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_Top)
animation.duration = 1.0
view_Body.layer.add(animation, forKey: "positionAnimation") //key自定義
}
- 旋轉動畫:
func rotateAnimation() {
let animation = CABasicAnimation.init(keyPath: "transform.rotation.z")
animation.toValue = NSNumber.init(value: Double.pi)
animation.duration = 0.1
animation.repeatCount = 1e100 //無限大重復次數
view_Body.layer.add(animation, forKey: "rotateAnimation")
}
- 縮放動畫
func scaleAnimation() {
let animation = CABasicAnimation.init(keyPath: "transform.scale")
animation.toValue = NSNumber.init(value: 2.0)
animation.duration = 1.0
view_Body.layer.add(animation, forKey: "scaleAnimation")
}
- 透明度動畫
func opacityAnimation() {
let animation = CABasicAnimation.init(keyPath: "opacity")
animation.fromValue = NSNumber.init(value: 1.0)
animation.toValue = NSNumber.init(value: 0.0)
animation.duration = 1.0
view_Body.layer.add(animation, forKey: "opacityAnimation")
}
- 背景色動畫
func backgroundColorAnimation() {
let animation = CABasicAnimation.init(keyPath: "backgroundColor")
animation.toValue = UIColor.green.cgColor //因為layer層動畫, 所以需要使用cgColor
animation.duration = 1.0
view_Body.layer.add(animation, forKey: "backgroundColorAnimation")
}
2. 關鍵幀動畫( CAKeyframeAnimation
)
CAKeyframeAnimation
和CABasicAnimation
都屬于CAPropertyAnimatin
的子類。不同的是CABasicAnimation
只能從一個數值(fromValue
)變換成另一個數值(toValue
),而CAKeyframeAnimation
則會使用一個數組(values
) 保存一組關鍵幀, 也可以給定一個路徑(path
)制作動畫。
CAKeyframeAnimation
主要有 三個 重要屬性:
- values:存放關鍵幀(
keyframe
)的數組,動畫對象會在指定的時間(duration
)內,依次顯示values數組中的每一個關鍵幀 . - path:可以設置一個
CGPathRef
或CGMutablePathRef
,讓層跟著路徑移動.path
只對CALayer
的anchorPoint
和position
起作用, 如果設置了path,那么values將被忽略. - keyTimes:可以為對應的關鍵幀指定對應的時間點,其取值范圍為0到1.0,
keyTimes
中的每一個時間值都對應values
中的每一幀.當keyTimes
沒有設置的時候,各個關鍵幀的時間是根據duration
平分的。
以抖動截圖為例, 效果圖如下:
動畫代碼如下:
- 關鍵幀動畫
func keyFrameAnimation() {
let animation = CAKeyframeAnimation.init(keyPath: "position")
let value_0 = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewWidthHeight)
let value_1 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 - margin_ViewWidthHeight)
let value_2 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 + margin_ViewMidPosition)
let value_3 = CGPoint.init(x: kScreenW * 2 / 3, y: kScreenH / 2 + margin_ViewMidPosition)
let value_4 = CGPoint.init(x: kScreenW * 2 / 3, y: kScreenH / 2 - margin_ViewWidthHeight)
let value_5 = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewWidthHeight)
animation.values = [value_0, value_1, value_2, value_3, value_4, value_5]
animation.duration = 2.0
view_Body.layer.add(animation, forKey: "keyFrameAnimation")
}
- 路徑動畫
func pathAnimation() {
let animation = CAKeyframeAnimation.init(keyPath: "position")
let path = UIBezierPath.init(arcCenter: CGPoint.init(x: kScreenW / 2, y: kScreenH / 2), radius: 60, startAngle: 0.0, endAngle: .pi * 2, clockwise: true)
animation.duration = 2.0
animation.path = path.cgPath
view_Body.layer.add(animation, forKey: "pathAnimation")
}
- 抖動動畫
func shakeAnimation() {
let animation = CAKeyframeAnimation.init(keyPath: "transform.rotation")
let value_0 = NSNumber.init(value: -Double.pi / 180 * 8)
let value_1 = NSNumber.init(value: Double.pi / 180 * 8)
animation.values = [value_0, value_1, value_0]
animation.duration = 1.0
animation.repeatCount = 1e100
view_Body.layer.add(animation, forKey: "shakeAnimation")
}
3. 組動畫( CAAnimationGroup
)
CAAnimationGroup
是 CAAnimation
的子類,可以保存一組動畫對象,可以保存基礎動畫、關鍵幀動畫等,數組中所有動畫對象可以同時并發運行, 也可以通過實踐設置為串行連續動畫.
效果截圖如下:
動畫代碼如下:
- 同時
//同時
func sameTimeAnimation() {
let animation_Position = CAKeyframeAnimation.init(keyPath: "position")
let value_0 = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewMidPosition)
let value_1 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 - margin_ViewMidPosition)
let value_2 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 + margin_ViewMidPosition)
let value_3 = CGPoint.init(x: kScreenW / 3 * 2, y: kScreenH / 2 + margin_ViewMidPosition)
let value_4 = CGPoint.init(x: kScreenW / 3 * 2, y: kScreenH / 2 - margin_ViewMidPosition)
let value_5 = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewMidPosition)
animation_Position.values = [value_0, value_1, value_2, value_3, value_4, value_5]
let animation_BGColor = CABasicAnimation.init(keyPath: "backgroundColor")
animation_BGColor.toValue = UIColor.green.cgColor
let animation_Rotate = CABasicAnimation.init(keyPath: "transform.rotation")
animation_Rotate.toValue = NSNumber.init(value: Double.pi * 4)
let animation_Group = CAAnimationGroup()
animation_Group.animations = [animation_Position, animation_BGColor, animation_Rotate]
animation_Group.duration = 4.0
view_Body.layer.add(animation_Group, forKey: "groupAnimation")
}
- 連續
//連續動畫 最主要的是處理好各個動畫時間的銜接
func goOnAnimation() {
//定義一個動畫開始的時間
let currentTime = CACurrentMediaTime()
let animation_Position = CABasicAnimation.init(keyPath: "position")
animation_Position.fromValue = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2)
animation_Position.toValue = CGPoint.init(x: kScreenW / 2, y: kScreenH / 2)
animation_Position.duration = 1.0
animation_Position.fillMode = "forwards" //只在前臺
animation_Position.isRemovedOnCompletion = false //切出界面再回來動畫不會停止
animation_Position.beginTime = currentTime
view_Body.layer.add(animation_Position, forKey: "positionAnimation")
let animation_Scale = CABasicAnimation.init(keyPath: "transform.scale")
animation_Scale.fromValue = NSNumber.init(value: 0.7)
animation_Scale.toValue = NSNumber.init(value: 2.0)
animation_Scale.duration = 1.0
animation_Scale.fillMode = "forwards"
animation_Scale.isRemovedOnCompletion = false
animation_Scale.beginTime = currentTime + 1.0
view_Body.layer.add(animation_Scale, forKey: "scaleAnimation")
let animation_Rotate = CABasicAnimation.init(keyPath: "transform.rotation")
animation_Rotate.toValue = NSNumber.init(value: Double.pi * 4)
animation_Rotate.duration = 1.0
animation_Rotate.fillMode = "forwards"
animation_Rotate.isRemovedOnCompletion = false
animation_Rotate.beginTime = currentTime + 2.0
view_Body.layer.add(animation_Rotate, forKey: "rotateAnimation")
}
4. 過渡動畫( CATransition
)
CATransition
是 CAAnimation
的子類,用于做過渡動畫或者 轉場 動畫,能夠為層提供移出屏幕和移入屏幕的動畫效果。
過渡動畫通過 type
設置不同的動畫效果, CATransition
有多種過渡效果, 但其實 Apple
官方的SDK只提供了四種:
- fade 淡出 默認
- moveIn 覆蓋原圖
- push 推出
- reveal 底部顯示出來
但私有API提供了其他很多非常炫的過渡動畫,如 cube
(立方旋轉)、suckEffect
(吸走)、oglFlip
(水平翻轉 沿y軸)、 rippleEffect
(滴水效果)、pageCurl
(卷曲翻頁 向上翻頁)、pageUnCurl
(卷曲翻頁 向下翻頁)、cameraIrisHollowOpen
(相機開啟)、cameraIrisHollowClose
(相機關閉)等。
注: 因 Apple
不提供維護,并且有可能造成你的app審核不通過, 所以不建議開發者們使用這些私有API.
效果如下:
翻頁動畫代碼如下:
func curlAnimation() {
let animation_Curl = CATransition()
animation_Curl.type = "pageCurl"
animation_Curl.subtype = "fromRight"
animation_Curl.duration = 1.0
view_Body.layer.add(animation_Curl, forKey: "curlAnimation")
}
5. 項目案例
- 進度條
效果如下:
進度條.gif
這里主要用到了CAShapeLayer
+CAGradientLayer
, 使用CAGradientLayer
畫進度圈(GPU執行, 高效), 然后使用CAGradientLayer
漸變色layer, 結合動畫顯示進度條.
代碼如下:
- UI視圖
func createView() {
label_Progress = UILabel()
label_Progress.text = ""
label_Progress.textAlignment = .center
label_Progress.font = UIFont.systemFont(ofSize: 25)
addSubview(label_Progress)
label_Progress.snp.makeConstraints { (make) in
make.centerX.centerY.equalTo(self)
make.width.equalTo(kScreenW)
make.height.equalTo(30)
}
layer_BackPath = CAShapeLayer()
layer_BackPath.fillColor = UIColor.clear.cgColor //填充顏色
layer_BackPath.strokeColor = UIColor.white.withAlphaComponent(0.5).cgColor //劃線顏色
layer_BackPath.lineWidth = width_MainPath
layer.addSublayer(layer_BackPath)
layer_MainPathLayer = CAShapeLayer()
layer_MainPathLayer.fillColor = UIColor.clear.cgColor
layer_MainPathLayer.strokeColor = UIColor.white.cgColor
layer_MainPathLayer.lineWidth = width_MainPath
layer.addSublayer(layer_MainPathLayer)
//漸變色
layer_Gradient = CAGradientLayer()
layer_Gradient.frame = CGRect.init(x: 0, y: 0, width: kScreenW, height: kScreenH)
layer_Gradient.type = "axial" //線性變化 默認目前只有這一個type
layer_Gradient.colors = [UIColor.init(hex: 0xf31414).cgColor, UIColor.init(hex: 0xf27200).cgColor, UIColor.init(hex: 0xffff00).cgColor, UIColor.init(hex: 0x2bee22).cgColor, UIColor.init(hex: 0x32a7eb).cgColor]
layer_Gradient.locations = [0, 0.3, 0.5, 0.7, 1] //每個漸變顏色的終止位置,這些值必須是遞增的,數組的長度和colors的長度最好一致
//startPoint endPoint 分別表示漸變層的起始位置和終止位置,這兩個點被定義在一個單元坐標空間,[0,0]表示左上角位置,[1,1]表示右下角位置,默認值分別是[.5,0] and [.5,1];
layer_Gradient.startPoint = CGPoint.init(x: 0, y: 0)
layer_Gradient.endPoint = CGPoint.init(x: 1, y: 0)
layer.addSublayer(layer_Gradient)
}
- 進度
func drawCircle(){
//貝塞爾曲線畫圓
let path_Back = UIBezierPath.init(arcCenter: CGPoint.init(x: kScreenW / 2, y: kScreenH / 2), radius: kScreenW / 5 - width_MainPath, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3, clockwise: true)
let path_Main = UIBezierPath.init(arcCenter: CGPoint.init(x: kScreenW / 2, y: kScreenH / 2), radius: kScreenW / 5 - width_MainPath + 3, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3, clockwise: true)
layer_BackPath.path = path_Back.cgPath
layer_MainPathLayer.path = path_Main.cgPath
layer_Gradient.mask = layer_MainPathLayer //用 layer_MainPathLayer 截取漸變層
//動畫 顯示路徑
let animation = CABasicAnimation.init(keyPath: "strokeEnd")
animation.duration = CFTimeInterval(Double(progress) * 0.01)
animation.fromValue = NSNumber.init(value: 0)
animation.toValue = NSNumber.init(value: Double(progress) * 0.01)
animation.fillMode = "forwards"
animation.isRemovedOnCompletion = false //完成后不刪除動畫
layer_MainPathLayer.add(animation, forKey: "strokeEndAnimation")
if progress > 0{
DispatchQueue.global().async {
self.timer_ProgressLabel = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(YTProgressView.progressLabelTimerAction), userInfo: nil, repeats: true)
RunLoop.current.run()
}
}else{
label_Progress.text = "0%"
}
}
func progressLabelTimerAction() {
DispatchQueue.main.async {
self.label_Progress.text = String(self.num_Progress) + "%"
}
if num_Progress >= progress{ //銷毀計時器
timer_ProgressLabel.invalidate()
timer_ProgressLabel = nil
}else{
num_Progress += 1
}
}
這里只展示了核心代碼, 詳細代碼可到github下載完整代碼: https://github.com/YTiOSer/YTAnimation
- 彈球, 仿Path菜單效果
- 點擊紅色按鈕,紅色按鈕旋轉。(旋轉動畫)
- 黑色小按鈕依次彈出,并且帶有旋轉效果。(位移動畫、旋轉動畫、組動畫)
- 點擊黑色小按鈕,其他按鈕消失,被點擊的黑色按鈕變大變淡消失。(縮放動畫、alpha動畫、組動畫)
tanqiu.gif
-
仿釘釘菜單效果
dingding.png
動畫實現用到了位移動畫和縮放動畫, 其實不難.
-
點贊
點贊.gif
三、總結
看完整篇文章相信你對 iOS
中的動畫有了一個詳細的了解, 其實單個動畫都是比較簡單的, 而復雜的動畫其實都是由一個個簡單的動畫組裝而成的,所以遇到比較難得動畫需求, 我們只要充分組裝不同的動畫,就能實現出滿意的效果.
好記性不如爛筆頭, 光說不練假把戲, 建議大家結合我的代碼, 自己邊看邊練習, 這樣才能記得牢, 才能轉換成自己的知識.
github: https://github.com/YTiOSer/YTAnimation
如果覺得對你還有些用,給個喜歡關注吧。你的支持是我繼續的動力。