iOS 動畫主要是指
Core Animation
框架,Core Animation
是iOS
和OS X
平臺上負(fù)責(zé)圖形渲染與動畫的基礎(chǔ)框架。Core Animation
可以作用于動畫視圖或者其他可視元素,可以完成動畫所需的大部分繪幀工作。Core Animation
系統(tǒng)已經(jīng)進行了封裝, 所以在使用的時候你只需要配置少量的動畫參數(shù)(如開始點的位置和結(jié)束點的位置)即可使用Core Animation
的多種動畫效果。Core Animation
將大部分實際的繪圖任務(wù)交給了圖形硬件(GPU)來處理,圖形硬件會加速圖形渲染的速度。這種自動化的圖形加速技術(shù)讓動畫擁有更高的幀率并且顯示效果更加平滑,不會加重CPU的負(fù)擔(dān)而影響程序的運行速度。
本文主要總結(jié)下平時常用的動畫, 如: 基礎(chǔ)動畫(CABasicAnimation
)、關(guān)鍵幀動畫(CAKeyframeAnimation
)、組動畫(CAAnimationGroup
)、過渡動畫(CATransition
), 最后也擴展了下, 做了進度條、貝塞爾曲線畫心??、彈球、釘釘效果、點贊等動畫,希望對大家有所幫助.
github: https://github.com/YTiOSer/YTAnimation
一、Core Animation類簡介
-
首先通過官方的
Core Animation
類圖了解下各個類之間的關(guān)系. 官網(wǎng)鏈接:Core Animation
Core Animation.png
建議詳細(xì)看下上圖, 這里對CAAnimation
的子類和相互關(guān)系及屬性介紹的比較詳細(xì), 看完后會對各個動畫類型有個大概的了解. 接下來詳細(xì)介紹下動畫的各個屬性及作用
- fromValue: 動畫的開始值(Any類型, 根據(jù)動畫不同可以是
CGPoint
、NSNumber等) - toValue: 動畫的結(jié)束值, 和fromValue類似
- beginTime: 動畫的開始時間
- duration : 動畫的持續(xù)時間
- repeatCount : 動畫的重復(fù)次數(shù)
- fillMode: 動畫的運行場景
- isRemovedOnCompletion: 完成后是否刪除動畫
- autoreverses: 執(zhí)行的動畫按照原動畫返回執(zhí)行
- path:關(guān)鍵幀動畫中的執(zhí)行路徑
- values: 關(guān)鍵幀動畫中的關(guān)鍵點數(shù)組
- animations: 組動畫中的動畫數(shù)組
- delegate : 動畫代理, 封裝了動畫的執(zhí)行和結(jié)束方法
- timingFunction: 控制動畫的顯示節(jié)奏, 系統(tǒng)提供五種值選擇,分別是:
1.kCAMediaTimingFunctionDefault
( 默認(rèn),中間快)
2.kCAMediaTimingFunctionLinear
(線性動畫)
3.kCAMediaTimingFunctionEaseIn
(先慢后快 慢進快出)
4.kCAMediaTimingFunctionEaseOut
(先塊后慢快進慢出)
5.kCAMediaTimingFunctionEaseInEaseOut
(先慢后快再慢) - type: 過渡動畫的動畫類型,系統(tǒng)提供了多種過渡動畫, 分別是:
1:fade
(淡出 默認(rèn))
2:moveIn
(覆蓋原圖)
3:push
(推出)
4:fade
(淡出 默認(rèn))
5:reveal
(底部顯示出來)
6:cube
(立方旋轉(zhuǎn))
7:suck
(吸走)
8:oglFlip
(水平翻轉(zhuǎn) 沿y軸)
9:ripple
(滴水效果)
10:curl
(卷曲翻頁 向上翻頁)
11:unCurl
(卷曲翻頁返回 向下翻頁)
12:caOpen
(相機開啟)
13:caClose
(相機關(guān)閉) - subtype : 過渡動畫的動畫方向, 系統(tǒng)提供了四種,分別是:
1.fromLeft
( 從左側(cè))
2.fromRight
(從右側(cè))
3.fromTop
(有上面)
4.fromBottom
(從下面)
二、Core Animation的使用
1. 基礎(chǔ)動畫( CABasicAnimation
)
基礎(chǔ)動畫主要提供了對于CALayer對象中的可變屬性進行簡單動畫的操作。比如:位移、旋轉(zhuǎn)、縮放、透明度、背景色等。
基礎(chǔ)動畫根據(jù)keyPath
來區(qū)分不同的動畫,, 系統(tǒng)提供了多個類型,如:transform.scale
(比例轉(zhuǎn)換)、transform.scale.x
、transform.scale.y
、transform.rotation
(旋轉(zhuǎn)) 、transform.rotation.x
(繞x軸旋轉(zhuǎn))、transform.rotation.y
(繞y軸旋轉(zhuǎn))、transform.rotation.z
(繞z軸旋轉(zhuǎn))、opacity
(透明度)、margin
、backgroundColor
(背景色)、cornerRadius
(圓角)、borderWidth
(邊框?qū)?、bounds
、contents
、contentsRect
、cornerRadius
、frame
、hidden
、mask
、masksToBounds
、shadowColor
(陰影色)、shadowOffset
、shadowOpacity
、shadowOpacity
, 在使用時候, 需要根據(jù)具體的需求選擇合適的.
效果圖如下:
- 位移動畫
func positionAnimation() {
let animation = CABasicAnimation.init(keyPath: "position") //keyPath為系統(tǒng)提供
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自定義
}
- 旋轉(zhuǎn)動畫:
func rotateAnimation() {
let animation = CABasicAnimation.init(keyPath: "transform.rotation.z")
animation.toValue = NSNumber.init(value: Double.pi)
animation.duration = 0.1
animation.repeatCount = 1e100 //無限大重復(fù)次數(shù)
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. 關(guān)鍵幀動畫( CAKeyframeAnimation
)
CAKeyframeAnimation
和CABasicAnimation
都屬于CAPropertyAnimatin
的子類。不同的是CABasicAnimation
只能從一個數(shù)值(fromValue
)變換成另一個數(shù)值(toValue
),而CAKeyframeAnimation
則會使用一個數(shù)組(values
) 保存一組關(guān)鍵幀, 也可以給定一個路徑(path
)制作動畫。
CAKeyframeAnimation
主要有 三個 重要屬性:
- values:存放關(guān)鍵幀(
keyframe
)的數(shù)組,動畫對象會在指定的時間(duration
)內(nèi),依次顯示values數(shù)組中的每一個關(guān)鍵幀 . - path:可以設(shè)置一個
CGPathRef
或CGMutablePathRef
,讓層跟著路徑移動.path
只對CALayer
的anchorPoint
和position
起作用, 如果設(shè)置了path,那么values將被忽略. - keyTimes:可以為對應(yīng)的關(guān)鍵幀指定對應(yīng)的時間點,其取值范圍為0到1.0,
keyTimes
中的每一個時間值都對應(yīng)values
中的每一幀.當(dāng)keyTimes
沒有設(shè)置的時候,各個關(guān)鍵幀的時間是根據(jù)duration
平分的。
以抖動截圖為例, 效果圖如下:
動畫代碼如下:
- 關(guān)鍵幀動畫
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
的子類,可以保存一組動畫對象,可以保存基礎(chǔ)動畫、關(guān)鍵幀動畫等,數(shù)組中所有動畫對象可以同時并發(fā)運行, 也可以通過實踐設(shè)置為串行連續(xù)動畫.
效果截圖如下:
動畫代碼如下:
- 同時
//同時
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")
}
- 連續(xù)
//連續(xù)動畫 最主要的是處理好各個動畫時間的銜接
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
的子類,用于做過渡動畫或者 轉(zhuǎn)場 動畫,能夠為層提供移出屏幕和移入屏幕的動畫效果。
過渡動畫通過 type
設(shè)置不同的動畫效果, CATransition
有多種過渡效果, 但其實 Apple
官方的SDK只提供了四種:
- fade 淡出 默認(rèn)
- moveIn 覆蓋原圖
- push 推出
- reveal 底部顯示出來
但私有API提供了其他很多非常炫的過渡動畫,如 cube
(立方旋轉(zhuǎn))、suckEffect
(吸走)、oglFlip
(水平翻轉(zhuǎn) 沿y軸)、 rippleEffect
(滴水效果)、pageCurl
(卷曲翻頁 向上翻頁)、pageUnCurl
(卷曲翻頁 向下翻頁)、cameraIrisHollowOpen
(相機開啟)、cameraIrisHollowClose
(相機關(guān)閉)等。
注: 因 Apple
不提供維護,并且有可能造成你的app審核不通過, 所以不建議開發(fā)者們使用這些私有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執(zhí)行, 高效), 然后使用CAGradientLayer
漸變色layer, 結(jié)合動畫顯示進度條.
代碼如下:
- 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" //線性變化 默認(rèn)目前只有這一個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] //每個漸變顏色的終止位置,這些值必須是遞增的,數(shù)組的長度和colors的長度最好一致
//startPoint endPoint 分別表示漸變層的起始位置和終止位置,這兩個點被定義在一個單元坐標(biāo)空間,[0,0]表示左上角位置,[1,1]表示右下角位置,默認(rèn)值分別是[.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
}
}
這里只展示了核心代碼, 詳細(xì)代碼可到github下載完整代碼: https://github.com/YTiOSer/YTAnimation
- 彈球, 仿Path菜單效果
- 點擊紅色按鈕,紅色按鈕旋轉(zhuǎn)。(旋轉(zhuǎn)動畫)
- 黑色小按鈕依次彈出,并且?guī)в行D(zhuǎn)效果。(位移動畫、旋轉(zhuǎn)動畫、組動畫)
- 點擊黑色小按鈕,其他按鈕消失,被點擊的黑色按鈕變大變淡消失。(縮放動畫、alpha動畫、組動畫)
tanqiu.gif
-
仿釘釘菜單效果
dingding.png
動畫實現(xiàn)用到了位移動畫和縮放動畫, 其實不難.
-
點贊
點贊.gif
三、總結(jié)
看完整篇文章相信你對 iOS
中的動畫有了一個詳細(xì)的了解, 其實單個動畫都是比較簡單的, 而復(fù)雜的動畫其實都是由一個個簡單的動畫組裝而成的,所以遇到比較難得動畫需求, 我們只要充分組裝不同的動畫,就能實現(xiàn)出滿意的效果.
好記性不如爛筆頭, 光說不練假把戲, 建議大家結(jié)合我的代碼, 自己邊看邊練習(xí), 這樣才能記得牢, 才能轉(zhuǎn)換成自己的知識.
github: https://github.com/YTiOSer/YTAnimation
如果覺得對你還有些用,給個喜歡關(guān)注吧。你的支持是我繼續(xù)的動力。