一、概念
CoreAnimation框架是基于OpenGL(ios13開始為metal)與CoreGraphics圖像處理框架的一個(gè)跨平臺的框架蚊俺,在CoreAnimation中大部分的動畫都是通過Layer層來實(shí)現(xiàn)的杆兵,通過CALayer膜宋,我們可以組織復(fù)雜的層級結(jié)構(gòu)。在CoreAnimation中磷醋,大多數(shù)的動畫效果是添加在圖層屬性的變化上坛缕,例如改變圖層的位置墓猎、大小、顏色赚楚、圓角半徑等毙沾。Layer層并不決定視圖的展現(xiàn),它只是存儲了視圖的幾何屬性狀態(tài)直晨。
二.CALayer與UIView
CALayer:繼承于NSObject,所以不具備響應(yīng)搀军,負(fù)責(zé)繪制膨俐、渲染圖形
UIView : 繼承于UIResponder勇皇,所以可以進(jìn)行事件響應(yīng),屬性CALayer負(fù)責(zé)圖形繪制與渲染
三.CoreAnimation類結(jié)構(gòu)
四焚刺、CAAnimation
動畫的父類(抽象類敛摘,虛類),擁有timingFunction
屬性乳愉,并且直接遵守CAMediaTiming協(xié)議
與CAAction協(xié)議
1.timingFunction
動畫的節(jié)奏兄淫,即動畫的快慢交替屯远,有以下幾個(gè)選項(xiàng):
- kCAMediaTimingFunctionLinear//線性節(jié)奏,就是勻速
- kCAMediaTimingFunctionEaseIn//淡入捕虽,緩慢加速進(jìn)入慨丐,然后勻速
- kCAMediaTimingFunctionEaseOut//淡出,勻速泄私,然后緩慢減速移除
- kCAMediaTimingFunctionEaseInEaseOut//淡入淡出房揭,結(jié)合以上兩者
- kCAMediaTimingFunctionDefault//默認(rèn)效果
timeFunction在swift中被定義為類型屬性,如下所示
public struct CAMediaTimingFunctionName : Hashable, Equatable, RawRepresentable {
public init(rawValue: String)
}
extension CAMediaTimingFunctionName {
/** Timing function names. **/
@available(iOS 2.0, *)
public static let linear: CAMediaTimingFunctionName
@available(iOS 2.0, *)
public static let easeIn: CAMediaTimingFunctionName
@available(iOS 2.0, *)
public static let easeOut: CAMediaTimingFunctionName
@available(iOS 2.0, *)
public static let easeInEaseOut: CAMediaTimingFunctionName
@available(iOS 3.0, *)
public static let `default`: CAMediaTimingFunctionName
}
OC方法設(shè)置timeFunction:
CAAnimation *ani = [CAAnimation animation];
ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
swift設(shè)置timeFunction:
let animation = CAAnimation()
animation.timingFunction = CAMediaTimingFunction(name: .linear)
2.delegate:CAAnimationDelegate
有以下兩個(gè)方法:
optional func animationDidStart(_ anim: CAAnimation)
optional func animationDidStop(_ anim: CAAnimation, finished flag: Bool)
前者為動畫的開始晌端,后者為動畫的結(jié)束
在動畫結(jié)束代理animationDidStop
中捅暴,有一個(gè)flag,為true表示動畫正常完成咧纠,為false的話表示沒有播放完成或被移除
3.isRemovedOnCompletion(OC中為removedOnCompletion)
isRemovedOnCompletion(OC中為removedOnCompletion)
是否在播放完成后移除蓬痒,默認(rèn)為true
.有的時(shí)候我們希望動畫播放完成,但是保留最終的播放效果時(shí)漆羔,這個(gè)屬性一定要改為false梧奢,否則無效。
4.CAMediaTiming協(xié)議:一個(gè)處理時(shí)間的協(xié)議
public protocol CAMediaTiming {
var beginTime: CFTimeInterval { get set }
var duration: CFTimeInterval { get set }
var speed: Float { get set }
var timeOffset: CFTimeInterval { get set }
var repeatCount: Float { get set }
var repeatDuration: CFTimeInterval { get set }
var autoreverses: Bool { get set }
var fillMode: CAMediaTimingFillMode { get set }
}
CAMediaTiming協(xié)議中的時(shí)間處理都是基于父對象演痒,由于CALayer與CAAnimation都遵守了CAMediaTiming協(xié)議
粹断,所以他們都可能是CAMediaTiming協(xié)議中屬性的父對象,如果是將動畫設(shè)置到動畫組(CAAnimationGroup)中嫡霞,那么父對象是CAAnimation瓶埋,可以直接使用Double類型值進(jìn)行賦值,如 animation.beginTime = 1.0
诊沪;如果是將動畫直接添加到一個(gè)CALayer上养筒,那么父對象是layer,這個(gè)layer的時(shí)間線很可能是一個(gè)過去的時(shí)間端姚。即使你添加上時(shí)間得到的仍舊是一個(gè)過去的時(shí)間晕粪。那么這個(gè)時(shí)候我們的動畫就會顯示在動畫結(jié)束時(shí)的狀態(tài)。為了避免這種狀況渐裸,我們要修正我們的時(shí)間巫湘。代碼如下:
let time = layer.convertTime(CACurrentMediaTime(), from: nil)
animation.beginTime = time + 1.0
CACurrentMediaTime()
是馬赫時(shí)間,是全局時(shí)間昏鹃,也就是設(shè)備距離上次開機(jī)的時(shí)間尚氛。
convertTime
是獲取本地時(shí)間。該方法有兩個(gè)參數(shù)洞渤,第一個(gè)是時(shí)間阅嘶,第二個(gè)是時(shí)間所對應(yīng)的圖層。當(dāng)?shù)诙€(gè)參數(shù)為nil時(shí),則返回的就是第一個(gè)參數(shù)的值讯柔。這樣我們就能得到一個(gè)絕對時(shí)間抡蛙。
下面是對CAMediaTiming協(xié)議各屬性的詳細(xì)解釋:
-
beginTime
: 動畫開始時(shí)間,更確切應(yīng)該為滯后時(shí)間魂迄,就是當(dāng)動畫添加到layer中滯后多久開始播放粗截, 默認(rèn)為0.0 -
duration
: 動畫時(shí)長 默認(rèn)為0.0 -
speed
: 動畫速度,默認(rèn)為1.0捣炬。它是當(dāng)前對象的時(shí)間流速慈格。簡單來說,如果speed是2遥金,duration是3浴捆,那么經(jīng)過1.5秒,我的動畫已經(jīng)播放完成了稿械。s = v * t选泻,s就相當(dāng)于已經(jīng)播放的動畫,也就是長度為3個(gè)單位的動畫美莫,v相當(dāng)于每秒播放多少動畫页眯,即每秒播放兩個(gè)單位的動畫,所以1.5秒后動畫其實(shí)就播放完成了厢呵。注意:CALayer也有個(gè)speed的屬性窝撵,如果layer的speed設(shè)置為2,那么所有在這個(gè)layer上的動畫都將提速至兩倍襟铭,如果既設(shè)置layer的speed碌奉,又設(shè)置了animation的speed,那么速度為兩個(gè)值相乘寒砖。 -
timeOffset
: 表示將從動畫的第幾秒開始播放赐劣。比如一個(gè)duration = 3的動畫,timeOffset = 2哩都,那么此時(shí)我的動畫從第二秒開始播放魁兼,播放到第三秒動畫結(jié)束的狀態(tài)后,立即變?yōu)閯赢嫷某跏紶顟B(tài)漠嵌,也就是第0秒的播放狀態(tài)咐汞,繼續(xù)播放至第二秒的狀態(tài),然后動畫結(jié)束儒鹿。形象的說就好像你繞操場跑圈化撕,以前你都是從起點(diǎn)開始跑跑一圈;這回你從一半開始跑挺身,但是同樣跑一圈侯谁,過了起點(diǎn)就還要再跑半圈,就是這樣章钾。 -
repeatCount
:重復(fù)的次數(shù)墙贱。不停重復(fù)設(shè)置為 HUGE_VALF -
repeatDuration
: 設(shè)置動畫重復(fù)的時(shí)間。在該時(shí)間內(nèi)動畫一直執(zhí)行贱傀,不計(jì)次數(shù)惨撇。 -
autoreverses
: 動畫結(jié)束時(shí)是否執(zhí)行逆動畫,默認(rèn)為false -
fillMode
:播放結(jié)束后的狀態(tài)府寒,它有四個(gè)枚舉值:
extension CAMediaTimingFillMode {
@available(iOS 2.0, *)
public static let forwards: CAMediaTimingFillMode
@available(iOS 2.0, *)
public static let backwards: CAMediaTimingFillMode
@available(iOS 2.0, *)
public static let both: CAMediaTimingFillMode
@available(iOS 2.0, *)
public static let removed: CAMediaTimingFillMode
}
(1). kCAFillModeForwards
(swift中為CAMediaTimingFillMode.forwards
):保持結(jié)束時(shí)狀態(tài)魁衙,需要保持結(jié)束時(shí)的狀態(tài),需要將removedOnCompletion的設(shè)置為false株搔,removedOnCompletion的為true剖淀,即動畫完成后移除
(2). kCAFillModeBackwards
(swift中為CAMediaTimingFillMode.backwards
):保持開始時(shí)狀態(tài),設(shè)置為該值纤房,將會立即執(zhí)行動畫的第一幀纵隔,不論是否設(shè)置了 beginTime屬性。
(3). kCAFillModeBoth
(swift中為CAMediaTimingFillMode.both
):保持兩者炮姨,實(shí)際使用中與kCAFillModeBackwards相同
(4). kCAFillModeRemoved
(swift中為CAMediaTimingFillMode.removed
):移除捌刮,默認(rèn)為這個(gè)值
,動畫將在設(shè)置的 beginTime 開始執(zhí)行(如沒有設(shè)置beginTime屬性舒岸,則動畫立即執(zhí)行)在验,動畫執(zhí)行完成后將會layer的改變恢復(fù)原狀鄙早。
五、CAPropertyAnimation(屬性動畫)
CAPropertyAnimation,屬性動畫辨嗽,它繼承于CAAnimation,也是一個(gè)抽象類庆亡,實(shí)現(xiàn)實(shí)際的動畫是運(yùn)用它的兩個(gè)子類CABasicAnimation與CAKeyframeAnimation
想知道CALayer的屬性是否可以用于keyPath卖擅,一個(gè)很簡單的辦法是看Apple的注釋,只要在注釋最后寫有Animatable的典尾,都可以用于作動畫役拴,如圖所示
六、CABasicAnimation(基礎(chǔ)動畫)
CABasicAnimation用來創(chuàng)建基于兩個(gè)狀態(tài)的動畫钾埂,你只需要給出兩個(gè)狀態(tài)河闰,一個(gè)初始狀態(tài)一個(gè)終止?fàn)顟B(tài),系統(tǒng)自動為你將中間的動畫補(bǔ)全褥紫。主要用于縮放姜性、平移和旋轉(zhuǎn)等簡單動畫。
open class CABasicAnimation : CAPropertyAnimation {
open var fromValue: Any?
open var toValue: Any?
open var byValue: Any?
}
屬性:
fromValue : 動畫的初始狀態(tài)
toValue : 動畫的結(jié)束狀態(tài)
byValue : 動畫狀態(tài)的增量
有如下幾種情況:
fromValue和toValue不為nil髓考,keyPath屬性值在fromValue與toValue之間漸變
fromValue和byValue不為nil部念,keyPath屬性值在fromValue與(fromValue+byValue)之間漸變
byValue和toValue不為nil,keyPath屬性值在(toValue-byValue)與toValue之間漸變
fromValue不為nil,keyPath屬性值在fromValue與圖層對應(yīng)當(dāng)前值之間漸變
toValue不為nil儡炼,keyPath屬性值在圖層對應(yīng)當(dāng)前值與toValue之間漸變
byValue不為nil妓湘,keyPath屬性值在圖層對應(yīng)當(dāng)前值與(圖層對應(yīng)當(dāng)前值+byValue)之間漸變
注意:value的值可以設(shè)置為CATransform3D的對象,實(shí)現(xiàn)3D動畫效果乌询!
以下是一個(gè)簡單的位移動畫的代碼:
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
view.addSubview(redView)
let animation = CABasicAnimation(keyPath: "position")
animation.isRemovedOnCompletion = false
animation.fillMode = CAMediaTimingFillMode.forwards
animation.autoreverses = true
animation.repeatCount = HUGE//OC使用MAXFLOAT 或者HUGE_VALF
animation.duration = 5
animation.fromValue = CGPoint(x: 50, y: 50)//可以使用NSValue封裝榜贴,也可以不用
animation.toValue = view.center//可以使用NSValue封裝,也可以不用
//add(:,forKey:)函數(shù)對動畫進(jìn)行了一次copy妹田,然后把copy的這份添加在了圖層上
redView.layer.add(animation, forKey: "translate")
//此處再設(shè)置延遲時(shí)間唬党,因?yàn)橹按嬖趌ayer上的animation為copy的,所以無效
animation.beginTime = CACurrentMediaTime() + 1
旋轉(zhuǎn)動畫:
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//默認(rèn)圍繞z軸旋轉(zhuǎn)鬼佣,也可指定繞某個(gè)軸旋轉(zhuǎn):transform.rotation.y(圍繞y軸旋轉(zhuǎn))
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.isRemovedOnCompletion = false
animation.fillMode = CAMediaTimingFillMode.forwards
animation.autoreverses = true
animation.repeatCount = HUGE//OC使用MAXFLOAT 或者HUGE_VALF
animation.duration = 5
animation.fromValue = 0
animation.toValue = Double.pi * 2.0
//add(:,forKey:)函數(shù)對動畫進(jìn)行了一次copy驶拱,然后把copy的這份添加在了圖層上
redView.layer.add(animation, forKey: "rotation")
//此處再設(shè)置延遲時(shí)間,因?yàn)橹按嬖趌ayer上的animation為copy的晶衷,所以無效
animation.beginTime = CACurrentMediaTime() + 1
縮放動畫:
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//這樣設(shè)置表示x,y,z三個(gè)方向進(jìn)行縮放蓝纲,也可指定只縮放某個(gè)軸的方向,如transform.scale.x(縮放x軸方向的值)
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.isRemovedOnCompletion = false
animation.fillMode = CAMediaTimingFillMode.forwards
animation.autoreverses = true
animation.repeatCount = HUGE//OC使用MAXFLOAT 或者HUGE_VALF
animation.duration = 3
//僅僅設(shè)置了fromValue,動畫將在fromValue與圖層當(dāng)前值之間漸變
animation.fromValue = 0.2
//add(:,forKey:)函數(shù)對動畫進(jìn)行了一次copy房铭,然后把copy的這份添加在了圖層上
redView.layer.add(animation, forKey: "scale")
//此處再設(shè)置延遲時(shí)間驻龟,因?yàn)橹按嬖趌ayer上的animation為copy的,所以無效
animation.beginTime = CACurrentMediaTime() + 1
transform動畫:
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//transform動畫
let animation = CABasicAnimation(keyPath: "transform")
animation.isRemovedOnCompletion = false
animation.fillMode = CAMediaTimingFillMode.forwards
animation.autoreverses = true
animation.repeatCount = HUGE//OC使用MAXFLOAT 或者HUGE_VALF
animation.duration = 5
var transform = CATransform3DMakeRotation(CGFloat(Double.pi), 1, 0, 0)
//給一個(gè)透視效果缸匪,讓效果更清晰
transform.m34 = -1.0/600.0
//只設(shè)置了byValue翁狐,動畫將在當(dāng)前狀態(tài)和當(dāng)前狀態(tài)+byValue之間漸變
animation.byValue = transform
//add(:,forKey:)函數(shù)對動畫進(jìn)行了一次copy,然后把copy的這份添加在了圖層上
redView.layer.add(animation, forKey: "rotation")
//此處再設(shè)置延遲時(shí)間凌蔬,因?yàn)橹按嬖趌ayer上的animation為copy的露懒,所以無效
animation.beginTime = CACurrentMediaTime() + 1
七、CAKeyframeAnimation(關(guān)鍵幀動畫)
.linear
為默認(rèn)值砂心,表示當(dāng)關(guān)鍵幀為座標(biāo)點(diǎn)的時(shí)候,關(guān)鍵幀之間直接直線相連進(jìn)行插值計(jì)算;.discrete
離散的,就是不進(jìn)行插值計(jì)算,所有關(guān)鍵幀直接逐個(gè)進(jìn)行顯示;.paced
使得動畫均勻進(jìn)行,而不是按keyTimes設(shè)置的或者按關(guān)鍵幀平分時(shí)間,此時(shí)keyTimes和timingFunctions無效;.cubic
對關(guān)鍵幀為座標(biāo)點(diǎn)的關(guān)鍵幀進(jìn)行圓滑曲線相連后插值計(jì)算,對于曲線的形狀還可以通過tensionValues,continuityValues,biasValues來進(jìn)行調(diào)整自定義,這里的數(shù)學(xué)原理是Kochanek-Bartels spline,這里的主要目的是使得運(yùn)行的軌跡變得圓滑;.cubicPaced
看這個(gè)名字就知道和.cubic有一定聯(lián)系,其實(shí)就是在.cubic的基礎(chǔ)上使得動畫運(yùn)行變得均勻,就是系統(tǒng)時(shí)間內(nèi)運(yùn)動的距離相同,此時(shí)keyTimes以及timingFunctions也是無效的.
CAKeyframeAnimation還有一個(gè)獨(dú)有的屬性懈词,rotationMode
。這個(gè)屬性的默認(rèn)是一個(gè)空值nil辩诞。有兩種可選值坎弯,kCAAnimationRotateAuto或者 kCAAnimationRotateAutoReverse。如果這個(gè)屬性設(shè)置成以上兩個(gè)值中的任意一個(gè)译暂,當(dāng)前l(fā)ayer都會始終保持朝向運(yùn)動方向抠忘,也就是跟隨運(yùn)動方向自動旋轉(zhuǎn)。
為什么特殊說一下這個(gè)屬性呢外永?因?yàn)槲覀儠r(shí)常相對一個(gè)物體做旋轉(zhuǎn)動畫崎脉,然而我們又不單純的想以layer的中心坐旋轉(zhuǎn),很多時(shí)候可能是layer的某個(gè)端點(diǎn)伯顶。這個(gè)時(shí)候你有三種選擇:
更改錨點(diǎn)
更改你的layer層
結(jié)合移動和轉(zhuǎn)動
更改錨點(diǎn)就是將錨點(diǎn)移至你想旋轉(zhuǎn)的旋轉(zhuǎn)中心囚灼。但是其實(shí)不建議你修改錨點(diǎn)骆膝。因?yàn)殄^點(diǎn)是一個(gè)layer層的參考點(diǎn)。當(dāng)你修改錨點(diǎn)以后將會影響layer的所有相關(guān)屬性以至于造成一些你所不希望的后果灶体。
更改layer層就是擴(kuò)展當(dāng)前l(fā)ayer區(qū)域阅签,以透明區(qū)域填補(bǔ)空白區(qū)域,強(qiáng)行讓你所期望的端點(diǎn)成為旋轉(zhuǎn)中心赃春。這個(gè)方法實(shí)在笨拙愉择,不優(yōu)雅劫乱。
結(jié)合移動和轉(zhuǎn)動就是其實(shí)將以端點(diǎn)的轉(zhuǎn)動拆分成錨點(diǎn)的轉(zhuǎn)動及錨點(diǎn)的弧線運(yùn)動去模擬端點(diǎn)的轉(zhuǎn)動织中。這個(gè)方法是三者中最優(yōu)雅的了,最起碼不是那些取巧的方法衷戈。
但是有了rotationMode這個(gè)屬性狭吼,這三個(gè)方法都不用了。你只需要一個(gè)錨點(diǎn)的弧線動畫以及rotationMode = .rotateAuto
就可以了殖妇,這樣就大大的簡化了我們的代碼量刁笙。
一個(gè)簡單的位移動畫,說明了未設(shè)置keyTimes屬性時(shí)谦趣,默認(rèn)的時(shí)間點(diǎn)一致疲吸,那么位移距離越長的,速度就越快
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//位移動畫
let animation = CAKeyframeAnimation(keyPath: "position")
animation.duration = 5
//分別將中心點(diǎn)移動到這三個(gè)point
animation.values = [CGPoint(x: 100, y: 100),CGPoint(x: 300, y: 200),CGPoint(x: 100, y: 800)]
/*
由于沒有設(shè)置keyTimes屬性的值前鹅,從{100,100}到{300,200}和從{300,200}到{100,800}之間分配的時(shí)間一致摘悴,都為2.5秒,而第二段的距離更長舰绘,所以可以看到第二段的速度會更快
*/
animation.isRemovedOnCompletion = false
animation.autoreverses = true
animation.repeatCount = HUGE
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "translate")
再添加上時(shí)間節(jié)點(diǎn)
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//位移動畫
let animation = CAKeyframeAnimation(keyPath: "position")
animation.duration = 5
//分別將中心點(diǎn)移動到這三個(gè)point
animation.values = [CGPoint(x: 100, y: 100),CGPoint(x: 300, y: 200),CGPoint(x: 100, y: 800)]
/* 添加上時(shí)間 可以看到第一段動畫速度變快了蹂喻,第二段動畫速度變慢了 */
animation.keyTimes = [NSNumber(value: 0.0),NSNumber(value: 0.3),NSNumber(value: 1.0)]
animation.autoreverses = true
animation.repeatCount = HUGE
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "translate")
發(fā)現(xiàn)在CGPoint(x: 300, y: 200)位置稍微有個(gè)停頓,不夠圓潤捂寿,我們再設(shè)置animation.calculationMode = .cubicPaced口四,讓動畫更平滑
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//位移動畫
let animation = CAKeyframeAnimation(keyPath: "position")
animation.duration = 5
//分別將中心點(diǎn)移動到這三個(gè)point
animation.values = [CGPoint(x: 100, y: 100),CGPoint(x: 300, y: 200),CGPoint(x: 100, y: 800)]
/* 添加上時(shí)間 可以看到第一段動畫速度變快了,第二段動畫速度變慢了 */
animation.keyTimes = [NSNumber(value: 0.0),NSNumber(value: 0.3),NSNumber(value: 1.0)]
animation.isRemovedOnCompletion = false
animation.autoreverses = true
animation.repeatCount = HUGE
/* 添加上計(jì)算模式屬性秦陋,讓動畫更平滑 */
animation.calculationMode = .cubicPaced
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "translate")
設(shè)置path屬性蔓彩,跟著預(yù)定軌跡運(yùn)動,設(shè)置path屬性后驳概,values自動失效
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//圍繞視圖中心點(diǎn)轉(zhuǎn)圈 使用path
let animation = CAKeyframeAnimation(keyPath: "position")
animation.duration = 5
//分別將中心點(diǎn)移動到這三個(gè)point
animation.values = [CGPoint(x: 100, y: 100),CGPoint(x: 300, y: 200),CGPoint(x: 100, y: 800)]
let path = CGMutablePath()
path.addArc(center: view.center, radius: 100, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
/*設(shè)置了path之后赤嚼,values屬性的值自動失效 */
animation.path = path
animation.isRemovedOnCompletion = false
animation.repeatCount = HUGE
/* 添加上計(jì)算模式屬性,讓動畫更平滑 */
animation.calculationMode = .cubicPaced
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "rotate")
搖一搖動畫
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
let label = UILabel(frame: redView.bounds)
label.textAlignment = .center
label.textColor = .white
label.text = "搖一搖"
redView.addSubview(label)
view.addSubview(redView)
//搖一搖動畫
let animation = CAKeyframeAnimation(keyPath: "transform.rotation")
animation.duration = 0.3
let angle = Double.pi/6.0
animation.values = [angle,-angle]
animation.isRemovedOnCompletion = false
animation.repeatCount = HUGE
animation.autoreverses = true
/* 添加上計(jì)算模式屬性抡句,讓動畫更平滑 */
animation.calculationMode = .cubicPaced
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "rotation")
設(shè)置rotationMode畫M
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
let label = UILabel(frame: redView.bounds)
label.textAlignment = .center
label.textColor = .white
label.text = "畫M"
redView.addSubview(label)
view.addSubview(redView)
//使用rotationMode
let animation = CAKeyframeAnimation(keyPath: "position")
animation.duration = 5
let path = CGMutablePath()
path.move(to: CGPoint(x: 40, y: 300))
path.addLine(to:CGPoint(x: 80, y: 150))
path.addLine(to:CGPoint(x: 120, y: 300))
path.addLine(to:CGPoint(x: 160, y: 150))
path.addLine(to:CGPoint(x: 200, y: 300))
animation.path = path
animation.isRemovedOnCompletion = false
animation.repeatCount = HUGE
animation.autoreverses = true
//使用rotationMode
animation.rotationMode = .rotateAuto
/* 添加上計(jì)算模式屬性探膊,讓動畫更平滑 */
animation.calculationMode = .cubicPaced
animation.fillMode = .forwards
redView.layer.add(animation, forKey: "translate")
八、CASpringAnimation(彈簧動畫)
CASpringAnimation繼承自CABasicAnimation待榔,iOS9新出逞壁,專用于解決開發(fā)者關(guān)于彈簧動畫的這個(gè)需求而封裝的類
使用UIView的方法實(shí)現(xiàn)彈簧動畫
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
//放縮動畫
UIView.animate(withDuration: 2, delay: 1, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.3, options: [.autoreverse,.repeat], animations: {
redView.transform = CGAffineTransform(scaleX: 2, y: 2)
}) { (finished) in
}
使用CASpringAnimation實(shí)現(xiàn)彈簧動畫
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
redView.center = view.center
view.addSubview(redView)
let animation = CASpringAnimation(keyPath: "transform.scale")
animation.beginTime = CACurrentMediaTime() + 3
animation.mass = 0.1 //設(shè)置質(zhì)量流济,質(zhì)量越大,慣性越大
animation.damping = 1//設(shè)置阻尼系數(shù)腌闯,阻尼系數(shù)越大绳瘟,停止越快
animation.stiffness = 100 //設(shè)置剛性系數(shù),剛性系數(shù)越大姿骏,運(yùn)動越快
animation.initialVelocity = 0.3 //初始速率
animation.toValue = 2.0
animation.isRemovedOnCompletion = false
animation.repeatCount = HUGE
animation.autoreverses = true
animation.fillMode = .forwards
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.duration = animation.settlingDuration//設(shè)置動畫時(shí)長
redView.layer.add(animation, forKey: "scale")
九糖声、CATransition(轉(zhuǎn)場動畫)
CATransition是CAAnimation的子類,用于做轉(zhuǎn)場動畫分瘦,能夠?yàn)閷犹峁┮瞥銎聊缓鸵迫肫聊坏膭赢嬓Ч盒骸OS比Mac OS X的轉(zhuǎn)場動畫效果少一點(diǎn)。它通常用于通過CALayer來控制UIView內(nèi)部子控件的過渡動畫嘲玫,比如刪除子控件悦施、添加子控件、切換兩個(gè)子控件等去团。
轉(zhuǎn)場動畫的過渡方式:
在OC中抡诞,因?yàn)閠ype是一個(gè)NSString 類型的別名,可以用常量如kCATransitionFade給type賦值,也可以直接字符串如"fade"
來賦值土陪,swift中也可以使用CATransitionType(rawValue: "rippleEffect")
來賦值
-
fade
:默認(rèn)昼汗,交叉淡化過渡,不支持過渡方向鬼雀,OC中對應(yīng)的常量為kCATransitionFade
-
push
:新視圖把舊視圖推出去顷窒,OC中對應(yīng)的常量為kCATransitionPush
-
moveIn
:新視圖移到舊視圖上面,對應(yīng)的常量為kCATransitionMoveIn
-
reveal
:將舊視圖移開顯示下邊的新視圖取刃,對應(yīng)的常量為kCATransitionReveal
-
cube
:立方體反轉(zhuǎn)效果 -
oglFlip
:上下左右反轉(zhuǎn)效果 -
suckEffect
:收縮效果蹋肮,如一塊布被抽走,不支持過渡方向 -
rippleEffect
:滴水效果璧疗,不支持過渡方向 -
pageCurl
:向上翻頁效果 -
pageUnCurl
:向下翻頁效果 -
cameraIrisHollowOpen
:相機(jī)鏡頭打開效果坯辩,不支持過渡方向 -
cameraIrisHollowClose
:相機(jī)鏡頭關(guān)閉效果,不支持過渡方向
如果不需要動畫執(zhí)行整個(gè)過程(動畫執(zhí)行到中間部分就停止)崩侠,就可以指定startProgress漆魔,endProgress屬性。一般我們不設(shè)置却音,startProgress默認(rèn)為0.0改抡,endProgress默認(rèn)為1.0,即執(zhí)行整個(gè)動畫過程
示例1:一個(gè)簡單切換圖片的水波動畫
class ViewController: UIViewController {
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
private func configUI() {
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.center = view.center
imageView.image = UIImage(named: "chb_meiqian")
view.addSubview(imageView)
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 0, y: view.bounds.height-150, width: 50, height: 40)
btn.center = CGPoint(x: view.bounds.width/2.0, y: view.bounds.height-150+20)
btn.backgroundColor = .green
btn.setTitleColor(.white, for: .normal)
btn.setTitle("切換", for: .normal)
btn.addTarget(self, action: #selector(transtion(_:)), for: .touchUpInside)
view.addSubview(btn)
}
@objc private func transtion(_ sender : UIButton) {
//轉(zhuǎn)場動畫
let animation = CATransition()
animation.type = CATransitionType(rawValue: "rippleEffect")
//水波動畫不支持過渡方向系瓢,所以設(shè)置了過渡方向是無效的
animation.subtype = .fromTop
animation.fillMode = .forwards
animation.duration = 5
imageView.layer .add(animation, forKey: "transition")
imageView.image = UIImage(named: "chb_tc_bg")
}
}
示例二:子控件交換
class ViewController: UIViewController {
let containerView = UIView()
let imageView1 = UIImageView()
let imageView2 = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
private func configUI() {
containerView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
containerView.center = view.center
view.addSubview(containerView)
imageView1.frame = containerView.bounds
imageView1.image = UIImage(named: "chb_meiqian")
containerView.addSubview(imageView1)
imageView2.frame = containerView.bounds
imageView2.image = UIImage(named: "chb_tc_bg")
containerView.addSubview(imageView2)
let btn1 = UIButton(type: .custom)
btn1.frame = CGRect(x: 0, y: view.bounds.height-150, width: 50, height: 40)
btn1.center = CGPoint(x: view.bounds.width/2.0, y: view.bounds.height-150+20)
btn1.backgroundColor = .green
btn1.setTitleColor(.white, for: .normal)
btn1.setTitle("覆蓋", for: .normal)
btn1.addTarget(self, action: #selector(moveIn(_:)), for: .touchUpInside)
view.addSubview(btn1)
let btn2 = UIButton(type: .custom)
var rect = btn1.frame
rect.origin.x = btn1.frame.minX - 30 - 50
btn2.frame = rect
btn2.backgroundColor = .green
btn2.setTitleColor(.white, for: .normal)
btn2.setTitle("旋轉(zhuǎn)", for: .normal)
btn2.addTarget(self, action: #selector(cube(_:)), for: .touchUpInside)
view.addSubview(btn2)
let btn3 = UIButton(type: .custom)
rect = btn1.frame
rect.origin.x = btn1.frame.maxX + 30
btn3.frame = rect
btn3.backgroundColor = .green
btn3.setTitleColor(.white, for: .normal)
btn3.setTitle("揭開", for: .normal)
btn3.addTarget(self, action: #selector(pageCurl(_:)), for: .touchUpInside)
view.addSubview(btn3)
}
@objc private func moveIn(_ sender : UIButton) {
//覆蓋動畫
let animation = CATransition()
animation.type = .moveIn
animation.subtype = .fromBottom
animation.fillMode = .forwards
animation.duration = 5
containerView.layer .add(animation, forKey: "moveIn")
containerView.exchangeSubview(at: 0, withSubviewAt: 1)
}
@objc private func cube(_ sender : UIButton) {
//立方體旋轉(zhuǎn)動畫
let animation = CATransition()
animation.type = CATransitionType(rawValue: "cube")
animation.subtype = .fromBottom
animation.fillMode = .forwards
animation.duration = 5
containerView.layer .add(animation, forKey: "cube")
containerView.exchangeSubview(at: 0, withSubviewAt: 1)
}
@objc private func pageCurl(_ sender : UIButton) {
//頁面揭開
let animation = CATransition()
animation.type = CATransitionType(rawValue: "pageCurl")
animation.subtype = .fromBottom
animation.fillMode = .forwards
animation.duration = 5
containerView.layer .add(animation, forKey: "pageCurl")
containerView.exchangeSubview(at: 0, withSubviewAt: 1)
}
}
轉(zhuǎn)場動畫有個(gè)特點(diǎn)是必須和轉(zhuǎn)場代碼寫在一起阿纤,官方并沒有給出轉(zhuǎn)場的概念到底是什么,一般來講夷陋,有如下三個(gè)是轉(zhuǎn)場代碼:
- UIImageView切換圖片,如示例一
- UIViewController的push和modal
- UIView對象調(diào)用exchangeSubview(at index1: Int, withSubviewAt index2: Int)方法欠拾,如示例二
十胰锌、CAAnimationGroup(動畫組)
動畫組是CAAnimation的子類,動畫組的特點(diǎn)是可以保存一組動畫對象藐窄,將CAAnimationGroup加入圖層后资昧,組中對象可以同時(shí)并發(fā)運(yùn)行
CAAnimationGroup只有一個(gè)專有屬性animations
,用來保存一組并發(fā)執(zhí)行的動畫
案例:旋轉(zhuǎn)+位移+放縮
class ViewController: UIViewController {
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
private func configUI() {
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.center = view.center
imageView.backgroundColor = .green
view.addSubview(imageView)
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 0, y: view.bounds.height-150, width: 50, height: 40)
btn.center = CGPoint(x: view.bounds.width/2.0, y: view.bounds.height-150+20)
btn.backgroundColor = .green
btn.setTitleColor(.white, for: .normal)
btn.setTitle("切換", for: .normal)
btn.addTarget(self, action: #selector(animationGroupBegin(_:)), for: .touchUpInside)
view.addSubview(btn)
}
@objc private func animationGroupBegin(_ sender : UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
//位移動畫
let transition = CABasicAnimation(keyPath: "position")
transition.isRemovedOnCompletion = false
transition.toValue = CGPoint(x: 50, y: 50)
transition.autoreverses = true
//旋轉(zhuǎn)動畫
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fillMode = .forwards
rotation.fromValue = 0
rotation.toValue = Double.pi * 2.0
rotation.beginTime = 1
let scale = CABasicAnimation(keyPath: "transform.scale")
scale.toValue = 0.1
scale.beginTime = 1.5
let group = CAAnimationGroup()
group.animations = [transition,rotation,scale]
group.fillMode = .forwards
group.repeatCount = HUGE
group.autoreverses = true
group.duration = 3
imageView.layer.add(group, forKey: "group")
} else {
imageView.layer.removeAllAnimations()
}
}
}