iOS 動(dòng)畫主要是指
Core Animation
框架,Core Animation
是iOS
和OS X
平臺(tái)上負(fù)責(zé)圖形渲染與動(dòng)畫的基礎(chǔ)框架蓖柔。Core Animation
可以作用于動(dòng)畫視圖或者其他可視元素盟劫,可以完成動(dòng)畫所需的大部分繪幀工作貌夕。Core Animation
系統(tǒng)已經(jīng)進(jìn)行了封裝, 所以在使用的時(shí)候你只需要配置少量的動(dòng)畫參數(shù)(如開始點(diǎn)的位置和結(jié)束點(diǎn)的位置)即可使用Core Animation
的多種動(dòng)畫效果急凰。Core Animation
將大部分實(shí)際的繪圖任務(wù)交給了圖形硬件(GPU)來處理十气,圖形硬件會(huì)加速圖形渲染的速度尿瞭。這種自動(dòng)化的圖形加速技術(shù)讓動(dòng)畫擁有更高的幀率并且顯示效果更加平滑乾蛤,不會(huì)加重CPU的負(fù)擔(dān)而影響程序的運(yùn)行速度邑滨。
本文主要總結(jié)下平時(shí)常用的動(dòng)畫, 如: 基礎(chǔ)動(dòng)畫(CABasicAnimation
)日缨、關(guān)鍵幀動(dòng)畫(CAKeyframeAnimation
)、組動(dòng)畫(CAAnimationGroup
)掖看、過渡動(dòng)畫(CATransition
), 最后也擴(kuò)展了下, 做了進(jìn)度條匣距、貝塞爾曲線畫心??、彈球哎壳、釘釘效果毅待、點(diǎn)贊等動(dòng)畫,希望對(duì)大家有所幫助.
github: https://github.com/YTiOSer/YTAnimation
一、Core Animation類簡介
-
首先通過官方的
Core Animation
類圖了解下各個(gè)類之間的關(guān)系. 官網(wǎng)鏈接:Core Animation
建議詳細(xì)看下上圖, 這里對(duì)CAAnimation
的子類和相互關(guān)系及屬性介紹的比較詳細(xì), 看完后會(huì)對(duì)各個(gè)動(dòng)畫類型有個(gè)大概的了解. 接下來詳細(xì)介紹下動(dòng)畫的各個(gè)屬性及作用
- fromValue: 動(dòng)畫的開始值(Any類型, 根據(jù)動(dòng)畫不同可以是
CGPoint
归榕、NSNumber等) - toValue: 動(dòng)畫的結(jié)束值, 和fromValue類似
- beginTime: 動(dòng)畫的開始時(shí)間
- duration : 動(dòng)畫的持續(xù)時(shí)間
- repeatCount : 動(dòng)畫的重復(fù)次數(shù)
- fillMode: 動(dòng)畫的運(yùn)行場景
- isRemovedOnCompletion: 完成后是否刪除動(dòng)畫
- autoreverses: 執(zhí)行的動(dòng)畫按照原動(dòng)畫返回執(zhí)行
- path:關(guān)鍵幀動(dòng)畫中的執(zhí)行路徑
- values: 關(guān)鍵幀動(dòng)畫中的關(guān)鍵點(diǎn)數(shù)組
- animations: 組動(dòng)畫中的動(dòng)畫數(shù)組
- delegate : 動(dòng)畫代理, 封裝了動(dòng)畫的執(zhí)行和結(jié)束方法
- timingFunction: 控制動(dòng)畫的顯示節(jié)奏, 系統(tǒng)提供五種值選擇尸红,分別是:
1.kCAMediaTimingFunctionDefault
( 默認(rèn),中間快)
2.kCAMediaTimingFunctionLinear
(線性動(dòng)畫)
3.kCAMediaTimingFunctionEaseIn
(先慢后快 慢進(jìn)快出)
4.kCAMediaTimingFunctionEaseOut
(先塊后慢快進(jìn)慢出)
5.kCAMediaTimingFunctionEaseInEaseOut
(先慢后快再慢) - type: 過渡動(dòng)畫的動(dòng)畫類型刹泄,系統(tǒng)提供了多種過渡動(dò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
(相機(jī)開啟)
13:caClose
(相機(jī)關(guān)閉) - subtype : 過渡動(dòng)畫的動(dòng)畫方向, 系統(tǒng)提供了四種,分別是:
1.fromLeft
( 從左側(cè))
2.fromRight
(從右側(cè))
3.fromTop
(有上面)
4.fromBottom
(從下面)
二外里、Core Animation的使用
1. 基礎(chǔ)動(dòng)畫( CABasicAnimation
)
基礎(chǔ)動(dòng)畫主要提供了對(duì)于CALayer對(duì)象中的可變屬性進(jìn)行簡單動(dòng)畫的操作。比如:位移循签、旋轉(zhuǎn)级乐、縮放、透明度县匠、背景色等风科。
基礎(chǔ)動(dòng)畫根據(jù)keyPath
來區(qū)分不同的動(dòng)畫,, 系統(tǒng)提供了多個(gè)類型,如: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
, 在使用時(shí)候, 需要根據(jù)具體的需求選擇合適的.
效果圖如下:
- 位移動(dòng)畫
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)動(dòng)畫:
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")
}
- 縮放動(dòng)畫
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")
}
- 透明度動(dòng)畫
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")
}
- 背景色動(dòng)畫
func backgroundColorAnimation() {
let animation = CABasicAnimation.init(keyPath: "backgroundColor")
animation.toValue = UIColor.green.cgColor //因?yàn)閘ayer層動(dòng)畫, 所以需要使用cgColor
animation.duration = 1.0
view_Body.layer.add(animation, forKey: "backgroundColorAnimation")
}
2. 關(guān)鍵幀動(dòng)畫( CAKeyframeAnimation
)
CAKeyframeAnimation
和CABasicAnimation
都屬于CAPropertyAnimatin
的子類恋日。不同的是CABasicAnimation
只能從一個(gè)數(shù)值(fromValue
)變換成另一個(gè)數(shù)值(toValue
),而CAKeyframeAnimation
則會(huì)使用一個(gè)數(shù)組(values
) 保存一組關(guān)鍵幀, 也可以給定一個(gè)路徑(path
)制作動(dòng)畫吞获。
CAKeyframeAnimation
主要有 三個(gè) 重要屬性:
- values:存放關(guān)鍵幀(
keyframe
)的數(shù)組,動(dòng)畫對(duì)象會(huì)在指定的時(shí)間(duration
)內(nèi),依次顯示values數(shù)組中的每一個(gè)關(guān)鍵幀 . - path:可以設(shè)置一個(gè)
CGPathRef
或CGMutablePathRef
,讓層跟著路徑移動(dòng).path
只對(duì)CALayer
的anchorPoint
和position
起作用, 如果設(shè)置了path谚鄙,那么values將被忽略. - keyTimes:可以為對(duì)應(yīng)的關(guān)鍵幀指定對(duì)應(yīng)的時(shí)間點(diǎn),其取值范圍為0到1.0,
keyTimes
中的每一個(gè)時(shí)間值都對(duì)應(yīng)values
中的每一幀.當(dāng)keyTimes
沒有設(shè)置的時(shí)候,各個(gè)關(guān)鍵幀的時(shí)間是根據(jù)duration
平分的。
以抖動(dòng)截圖為例, 效果圖如下:
動(dòng)畫代碼如下:
- 關(guān)鍵幀動(dòng)畫
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")
}
- 路徑動(dòng)畫
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")
}
- 抖動(dòng)動(dòng)畫
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. 組動(dòng)畫( CAAnimationGroup
)
CAAnimationGroup
是 CAAnimation
的子類岖免,可以保存一組動(dòng)畫對(duì)象柠衅,可以保存基礎(chǔ)動(dòng)畫娩脾、關(guān)鍵幀動(dòng)畫等,數(shù)組中所有動(dòng)畫對(duì)象可以同時(shí)并發(fā)運(yùn)行, 也可以通過實(shí)踐設(shè)置為串行連續(xù)動(dòng)畫.
效果截圖如下:
動(dòng)畫代碼如下:
- 同時(shí)
//同時(shí)
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ù)動(dòng)畫 最主要的是處理好各個(gè)動(dòng)畫時(shí)間的銜接
func goOnAnimation() {
//定義一個(gè)動(dòng)畫開始的時(shí)間
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" //只在前臺(tái)
animation_Position.isRemovedOnCompletion = false //切出界面再回來動(dòng)畫不會(huì)停止
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. 過渡動(dòng)畫( CATransition
)
CATransition
是 CAAnimation
的子類傻盟,用于做過渡動(dòng)畫或者 轉(zhuǎn)場 動(dòng)畫,能夠?yàn)閷犹峁┮瞥銎聊缓鸵迫肫聊坏膭?dòng)畫效果嫂丙。
過渡動(dòng)畫通過 type
設(shè)置不同的動(dòng)畫效果, CATransition
有多種過渡效果, 但其實(shí) Apple
官方的SDK只提供了四種:
- fade 淡出 默認(rèn)
- moveIn 覆蓋原圖
- push 推出
- reveal 底部顯示出來
但私有API提供了其他很多非常炫的過渡動(dòng)畫娘赴,如 cube
(立方旋轉(zhuǎn))、suckEffect
(吸走)跟啤、oglFlip
(水平翻轉(zhuǎn) 沿y軸)诽表、 rippleEffect
(滴水效果)、pageCurl
(卷曲翻頁 向上翻頁)隅肥、pageUnCurl
(卷曲翻頁 向下翻頁)竿奏、cameraIrisHollowOpen
(相機(jī)開啟)、cameraIrisHollowClose
(相機(jī)關(guān)閉)等腥放。
注: 因 Apple
不提供維護(hù)泛啸,并且有可能造成你的app審核不通過, 所以不建議開發(fā)者們使用這些私有API.
效果如下:
翻頁動(dòng)畫代碼如下:
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. 項(xiàng)目案例
- 進(jìn)度條
效果如下:
這里主要用到了CAShapeLayer
+CAGradientLayer
, 使用CAGradientLayer
畫進(jìn)度圈(GPU執(zhí)行, 高效), 然后使用CAGradientLayer
漸變色layer, 結(jié)合動(dòng)畫顯示進(jìn)度條.
代碼如下:
- 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)目前只有這一個(gè)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] //每個(gè)漸變顏色的終止位置,這些值必須是遞增的秃症,數(shù)組的長度和colors的長度最好一致
//startPoint endPoint 分別表示漸變層的起始位置和終止位置候址,這兩個(gè)點(diǎn)被定義在一個(gè)單元坐標(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)
}
- 進(jìn)度
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 截取漸變層
//動(dòng)畫 顯示路徑
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 //完成后不刪除動(dòng)畫
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{ //銷毀計(jì)時(shí)器
timer_ProgressLabel.invalidate()
timer_ProgressLabel = nil
}else{
num_Progress += 1
}
}
這里只展示了核心代碼, 詳細(xì)代碼可到github下載完整代碼: https://github.com/YTiOSer/YTAnimation
- 彈球, 仿Path菜單效果
- 點(diǎn)擊紅色按鈕,紅色按鈕旋轉(zhuǎn)赔蒲。(旋轉(zhuǎn)動(dòng)畫)
- 黑色小按鈕依次彈出泌神,并且?guī)в行D(zhuǎn)效果。(位移動(dòng)畫舞虱、旋轉(zhuǎn)動(dòng)畫欢际、組動(dòng)畫)
- 點(diǎn)擊黑色小按鈕,其他按鈕消失矾兜,被點(diǎn)擊的黑色按鈕變大變淡消失损趋。(縮放動(dòng)畫、alpha動(dòng)畫椅寺、組動(dòng)畫)
-
仿釘釘菜單效果
動(dòng)畫實(shí)現(xiàn)用到了位移動(dòng)畫和縮放動(dòng)畫, 其實(shí)不難.
-
點(diǎn)贊
三浑槽、總結(jié)
看完整篇文章相信你對(duì) iOS
中的動(dòng)畫有了一個(gè)詳細(xì)的了解, 其實(shí)單個(gè)動(dòng)畫都是比較簡單的, 而復(fù)雜的動(dòng)畫其實(shí)都是由一個(gè)個(gè)簡單的動(dòng)畫組裝而成的,所以遇到比較難得動(dòng)畫需求, 我們只要充分組裝不同的動(dòng)畫返帕,就能實(shí)現(xiàn)出滿意的效果.
好記性不如爛筆頭, 光說不練假把戲, 建議大家結(jié)合我的代碼, 自己邊看邊練習(xí), 這樣才能記得牢, 才能轉(zhuǎn)換成自己的知識(shí).
github: https://github.com/YTiOSer/YTAnimation
如果覺得對(duì)你還有些用桐玻,給個(gè)喜歡關(guān)注吧。你的支持是我繼續(xù)的動(dòng)力荆萤。