iOS核心動畫詳解

一、概念

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é)圖形繪制與渲染

CALayer與UIView對比

三.CoreAnimation類結(jié)構(gòu)

核心動畫類結(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


CAPropertyAnimation屬性
常用keyPath

想知道CALayer的屬性是否可以用于keyPath卖擅,一個(gè)很簡單的辦法是看Apple的注釋,只要在注釋最后寫有Animatable的典尾,都可以用于作動畫役拴,如圖所示


CALayer可以作動畫的屬性

六、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)鍵幀動畫)

CAKeyframeAnimation屬性詳解
不同calculationMode過渡示意圖
  • .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è)需求而封裝的類


CASpringAnimation屬性講解

使用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)場動畫屬性講解

轉(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()
        }
        
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荆忍,一起剝皮案震驚了整個(gè)濱河市格带,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刹枉,老刑警劉巖叽唱,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘶卧,居然都是意外死亡尔觉,警方通過查閱死者的電腦和手機(jī)凉袱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門芥吟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人专甩,你說我怎么就攤上這事钟鸵。” “怎么了涤躲?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵棺耍,是天一觀的道長。 經(jīng)常有香客問我种樱,道長蒙袍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任嫩挤,我火速辦了婚禮害幅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岂昭。我一直安慰自己以现,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布约啊。 她就那樣靜靜地躺著邑遏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恰矩。 梳的紋絲不亂的頭發(fā)上记盒,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音外傅,去河邊找鬼纪吮。 笑死蹂午,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的彬碱。 我是一名探鬼主播豆胸,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼巷疼!你這毒婦竟也來了晚胡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嚼沿,失蹤者是張志新(化名)和其女友劉穎估盘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骡尽,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遣妥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攀细。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箫踩。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谭贪,靈堂內(nèi)的尸體忽然破棺而出境钟,到底是詐尸還是另有隱情,我是刑警寧澤俭识,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布慨削,位于F島的核電站,受9級特大地震影響套媚,放射性物質(zhì)發(fā)生泄漏缚态。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一堤瘤、第九天 我趴在偏房一處隱蔽的房頂上張望玫芦。 院中可真熱鬧,春花似錦宙橱、人聲如沸姨俩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环葵。三九已至,卻和暖如春宝冕,著一層夾襖步出監(jiān)牢的瞬間张遭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工地梨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菊卷,地道東北人缔恳。 一個(gè)月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像洁闰,于是被迫代替她去往敵國和親歉甚。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359