iOS 動(dòng)畫(huà)進(jìn)階

貓咪在上一篇文章中簡(jiǎn)單的介紹了UIView動(dòng)畫(huà)的入門(mén)和實(shí)現(xiàn)浅妆,那這篇文章,就來(lái)和大家一起來(lái)看一看更復(fù)雜的動(dòng)畫(huà)效果是怎么實(shí)現(xiàn)的呢障癌?先來(lái)看一張圖:

iOS中的圖形層次

正如我們看到的那樣凌外,越往上封裝度越高,動(dòng)畫(huà)的實(shí)現(xiàn)越簡(jiǎn)潔混弥,但是自由度低趴乡。

核心動(dòng)畫(huà)有以下幾類(lèi):
  • 圖層類(lèi)(CALayer
  • 動(dòng)畫(huà)和計(jì)時(shí)類(lèi) (CAAnimationCAMediaTiming
  • 布局和約束類(lèi)(CAConstraint
  • 事務(wù)類(lèi) (CATransaction

一、各個(gè)類(lèi)的概念介紹

1.圖層類(lèi) - CALayer

圖層類(lèi)是核心動(dòng)畫(huà)的基礎(chǔ)蝗拿,它是所有核心動(dòng)畫(huà)圖層類(lèi)的父類(lèi)晾捏。和UIView一樣,同樣有自己的視圖集合哀托,同樣有l(wèi)ayer惦辛、subLayer...也同樣擁有backgroundColor、frame等相似的屬性仓手,我們可以將UIView看做是一個(gè)特殊的CALayer胖齐。
它們之間的區(qū)別就是UIView可以響應(yīng)事件,layer最常用的就是設(shè)置圓角嗽冒、陰影呀伙、邊框等參數(shù)和實(shí)現(xiàn)動(dòng)畫(huà)。因此對(duì)view做動(dòng)畫(huà)實(shí)際上是對(duì)layer進(jìn)行操作添坊。

2.動(dòng)畫(huà)類(lèi) - CAAnimation

核心動(dòng)畫(huà)的動(dòng)畫(huà)類(lèi)使用基本的動(dòng)畫(huà)和關(guān)鍵幀動(dòng)畫(huà)把圖層的內(nèi)容和選取的屬性動(dòng) 畫(huà)的顯示出來(lái)剿另。所有核心動(dòng)畫(huà)的動(dòng)畫(huà)類(lèi)都是從 CAAnimation 類(lèi)繼承而來(lái)。CAAnimation 實(shí)現(xiàn)了 CAMediaTiming 協(xié)議贬蛙,提供了動(dòng)畫(huà)的持續(xù)時(shí)間雨女,速度,和重復(fù)計(jì)數(shù)阳准。 CAAnimation 也實(shí)現(xiàn)了 CAAction 協(xié)議氛堕。該協(xié)議為圖層觸發(fā)一個(gè)動(dòng)畫(huà)動(dòng)作提供了提供 標(biāo)準(zhǔn)化響應(yīng)。
推薦文章:iOS開(kāi)發(fā)CoreAnimation解讀之一——初識(shí)CoreAnimation核心動(dòng)畫(huà)編程

3.布局和約束類(lèi) - CAConstraint

核心動(dòng)畫(huà)的 CAConstraint 類(lèi) 是一個(gè)布局管理器野蝇,它可以指定子圖層類(lèi)限制于你指定的約束集合讼稚。每個(gè)約束 (CAConstraint 類(lèi)的實(shí)例封裝)描述層的幾何屬性(左,右绕沈,頂部或底部的邊緣或水 平或垂直中心)的關(guān)系乱灵,關(guān)系到其同級(jí)之一的幾何屬性層或 superlayer。

4.事務(wù)類(lèi) - CATransaction

CATransition 提供了一個(gè)圖層變化的過(guò)渡效果七冲,它能影響圖層的整個(gè)內(nèi)容。 動(dòng)畫(huà)進(jìn)行的時(shí)候淡入淡出(fade)规婆、推(push)澜躺、顯露(reveal)圖層的內(nèi)容蝉稳。這些過(guò)渡效 果可以擴(kuò)展到你自己定制的 Core Image 濾鏡。

二掘鄙、核心動(dòng)畫(huà)渲染框架

雖然核心動(dòng)畫(huà)的圖層和 Cocoa 的視圖在很大程度上沒(méi)有一定的相似性耘戚,但是他們 兩者最大的區(qū)別是,圖層不會(huì)直接渲染到屏幕上操漠。

在我們常用的模型-視圖-控制器(model-view-controller)概念里面 NSView 和 UIView 是典 型的視圖部分收津,但是在核心動(dòng)畫(huà)里面圖層是模型部分。圖層封裝了幾何浊伙、時(shí)間撞秋、可視 化屬性,同時(shí)它提供了圖層現(xiàn)實(shí)的內(nèi)容嚣鄙,但是實(shí)際顯示的過(guò)程則不是由它來(lái)完成吻贿。

三、CoreGraphics框架

介紹CoreGraphics的資料很多哑子,推薦給大家一篇我覺(jué)得比較好的: CoreGraphics入門(mén)

hamburger.gif
hamburger.gif
這是貓咪實(shí)現(xiàn)的兩個(gè)小動(dòng)畫(huà)舅列,上面hamburger的動(dòng)畫(huà)原址:用 Swift 制作一個(gè)漂亮的漢堡按鈕過(guò)渡動(dòng)畫(huà),給大家簡(jiǎn)單介紹一下下面播放按鈕的實(shí)現(xiàn)原理:
import CoreGraphics
import QuartzCore
import UIKit

class PlayButton: UIControl {

// MARK: - enum
// 按鈕的幾種動(dòng)畫(huà)效果
enum PlayAnimation {
    case rotateAndGrad
    case grad
    case breathe
}

// 播放狀態(tài)
enum PlayStatus {
    case buffering  // 緩沖
    case play       // 播放
    case pause      // 暫停
    case stopped    // 停止
}

// MARK: - private property
// 左右兩條線
private var top: CAShapeLayer! = CAShapeLayer()
private var bottom: CAShapeLayer! = CAShapeLayer()
private var rotate: CAShapeLayer! = CAShapeLayer()
// 菜單的起點(diǎn)和終點(diǎn)
private let menuStrokeStart: CGFloat = 0.325
private let menuStrokeEnd: CGFloat = 0.9
// 中線的起點(diǎn)和重點(diǎn)
private let playStrokeStart: CGFloat = 0.028
private let playStrokeEnd: CGFloat = 0.111

// MARK: - life cycle
override init(frame: CGRect) {
    super.init(frame: frame)
    
    // 繪制左右兩條線
    self.top.path = leftStroke
    self.bottom.path = rightStroke
    self.rotate.path = outline
    
    // 設(shè)置layer的相關(guān)屬性
    for layer in [self.top, self.bottom, self.rotate] {
        // 填充顏色
        layer?.fillColor = nil
        // 線條的顏色
        layer?.strokeColor = UIColor.white.cgColor
        // 線條寬度
        layer?.lineWidth = 4
        // 兩條線段相交時(shí)銳角斜面長(zhǎng)度
        layer?.miterLimit = 4
        // 線條首尾的外觀
        layer?.lineCap = kCALineCapRound

        // 設(shè)置layer的bounds
        let strokingPath = CGPath(__byStroking: (layer?.path!)!, transform: nil, lineWidth: 4, lineCap: .round, lineJoin: .miter, miterLimit: 4)
        layer?.bounds = (strokingPath?.boundingBoxOfPath)!
        // 設(shè)置行為
        layer?.actions = [
            "strokeStart": NSNull(),
            "strokeEnd": NSNull(),
            "transform": NSNull()
            
        ]
        
        self.layer.addSublayer(layer!)
    }
    
    // 設(shè)置左線的錨點(diǎn)和位置
    self.top.anchorPoint = CGPoint(x: 1, y: 1)
    self.top.position = CGPoint(x: 40, y: 19.25)
    // 設(shè)置右線的錨點(diǎn)和位置
    self.bottom.anchorPoint = CGPoint(x: 1, y: 0)
    self.bottom.position = CGPoint(x: 40, y: 18)
    // 設(shè)置中線的位置和起點(diǎn)和終點(diǎn)
    self.rotate.position = CGPoint(x: 29, y: 18)
    self.rotate.strokeStart = playStrokeStart
    self.rotate.strokeEnd = playStrokeEnd

}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - setter and getter

// 畫(huà)短直線 -> play
let leftStroke: CGPath = {
    let path = CGMutablePath()
    path.move(to: CGPoint(x: 2, y: 2))
    path.addLine(to: CGPoint(x: 2, y: 15))
    return path
}()
private let rightStroke: CGPath = {
    let path = CGMutablePath()
    path.move(to: CGPoint(x: 2, y: 12))
    path.addLine(to: CGPoint(x: 2, y: 25))
    return path
}()
// 外邊框圓 -> 中間
private let outline: CGPath = {
    let path = CGMutablePath()
    path.move(to: CGPoint(x: 29, y: 0))
    // 添加曲線
    path.addCurve(to: CGPoint(x: 27, y: 53), control1: CGPoint(x: 27, y: 31), control2: CGPoint(x: 27, y: 43.75))
    path.addCurve(to: CGPoint(x: 41.5, y: 3), control1: CGPoint(x: 75.92, y: 24.75), control2: CGPoint(x: 62.97, y: 3))
    path.addCurve(to: CGPoint(x: 16.5, y: 27), control1: CGPoint(x: 28.66, y: 3), control2: CGPoint(x: 16.5, y: 13.16))
    path.addCurve(to: CGPoint(x: 41.5, y: 52), control1: CGPoint(x: 16.5, y: 40.84), control2: CGPoint(x: 27.66, y: 52))
    path.addCurve(to: CGPoint(x: 66.5, y: 27), control1: CGPoint(x: 55.34, y: 52), control2: CGPoint(x: 66.5, y: 40.84))
    path.addCurve(to: CGPoint(x: 41.5, y: 3), control1: CGPoint(x: 66.5, y: 13.16), control2: CGPoint(x: 56.89, y: 3))
    path.addCurve(to: CGPoint(x: 16.5, y: 27), control1: CGPoint(x: 27.66, y: 3), control2: CGPoint(x: 16.5, y: 13.16))

    return path
}()

var playStatus: PlayStatus = .pause {
    didSet {
        let strokeStart = CABasicAnimation(keyPath: "strokeStart")
        let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
        
        // 動(dòng)畫(huà)
        if self.playStatus == .play {
            strokeStart.toValue = menuStrokeStart
            strokeStart.duration = 0.2
            strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
            
            strokeEnd.toValue = menuStrokeEnd
            strokeEnd.duration = 0.3
            strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
        } else {
            strokeStart.toValue = playStrokeStart
            strokeStart.duration = 0.2
            strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0, 0.5, 1.2)
            strokeStart.beginTime = CACurrentMediaTime() + 0.1
            strokeStart.fillMode = kCAFillModeBackwards
            
            strokeEnd.toValue = playStrokeEnd
            strokeEnd.duration = 0.3
            strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.3, 0.5, 0.9)
        }
        
        self.rotate.ocb_applyAnimation(strokeStart)
        self.rotate.ocb_applyAnimation(strokeEnd)
    
        // 設(shè)置豎線的變化
        let topTransform = CABasicAnimation(keyPath: "transform")
        topTransform.timingFunction = CAMediaTimingFunction(controlPoints: 0.5, -0.8, 0.5, 1.85)
        topTransform.duration = 0.2
        topTransform.fillMode = kCAFillModeBackwards
        
        let bottomTransform = topTransform.copy() as! CABasicAnimation
        
        if self.playStatus == .play {
            let translation = CATransform3DMakeTranslation(-2, 0, 0)
            
            topTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, -0.65, 0, 0, 1))
            topTransform.beginTime = CACurrentMediaTime() + 0.25
            
            bottomTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, 0.84, 0, 0, 1))
            bottomTransform.beginTime = CACurrentMediaTime() + 0.25
        } else {
            topTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
            topTransform.beginTime = CACurrentMediaTime() + 0.05
            
            bottomTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
            bottomTransform.beginTime = CACurrentMediaTime() + 0.05
        }
        
        self.top.ocb_applyAnimation(topTransform)
        self.bottom.ocb_applyAnimation(bottomTransform)
    }
    
}
}

對(duì)CALayer進(jìn)行擴(kuò)展

extension CALayer {
   func ocb_applyAnimation(_ animation: CABasicAnimation) {
      let copy = animation.copy() as! CABasicAnimation
    
      if copy.fromValue == nil {
          copy.fromValue = self.presentation()!.value(forKeyPath: copy.keyPath!)
      }
    
      self.add(copy, forKey: copy.keyPath)
      self.setValue(copy.toValue, forKeyPath:copy.keyPath!)
    }  
}

在控制器中卧蜓,我們創(chuàng)建一個(gè)播放按鈕:

class ViewController: UIViewController {

   var button: PlayButton! = nil

   override func viewDidLoad() {
      super.viewDidLoad()
      self.view.backgroundColor = UIColor(red: 38.0 / 255, green: 151.0 / 255, blue: 68.0 / 255, alpha: 1)
    
      button = PlayButton(frame: CGRect(x: 133, y: 133, width: 100, height: 100))
//        button.transform = CGAffineTransform.init(scaleX: 0.5, y: 0.5)  作用于view
      button.layer.transform =  CATransform3DMakeScale(0.8, 0.8, 1)
      button.addTarget(self, action: #selector(ViewController.toggle(_:)), for:.touchUpInside)
    
      view.addSubview(button)
    
   }

   override var preferredStatusBarStyle : UIStatusBarStyle  {
    return .lightContent
   }

   func toggle(_ sender: AnyObject!) {
     if button.playStatus == .play {
         button.playStatus = .pause
     } else if button.playStatus == .pause {
         button.playStatus = .play
     }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帐要,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弥奸,更是在濱河造成了極大的恐慌榨惠,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件其爵,死亡現(xiàn)場(chǎng)離奇詭異冒冬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)摩渺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)简烤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人摇幻,你說(shuō)我怎么就攤上這事横侦。” “怎么了绰姻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵枉侧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我狂芋,道長(zhǎng)榨馁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任帜矾,我火速辦了婚禮翼虫,結(jié)果婚禮上屑柔,老公的妹妹穿的比我還像新娘。我一直安慰自己珍剑,他們只是感情好掸宛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著招拙,像睡著了一般唧瘾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上别凤,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天饰序,我揣著相機(jī)與錄音,去河邊找鬼闻妓。 笑死菌羽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的由缆。 我是一名探鬼主播注祖,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼均唉!你這毒婦竟也來(lái)了是晨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舔箭,失蹤者是張志新(化名)和其女友劉穎罩缴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體层扶,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箫章,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镜会。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檬寂。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戳表,靈堂內(nèi)的尸體忽然破棺而出桶至,到底是詐尸還是另有隱情,我是刑警寧澤匾旭,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布镣屹,位于F島的核電站,受9級(jí)特大地震影響价涝,放射性物質(zhì)發(fā)生泄漏女蜈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伪窖。 院中可真熱鬧吏廉,春花似錦、人聲如沸惰许。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)汹买。三九已至,卻和暖如春聊倔,著一層夾襖步出監(jiān)牢的瞬間晦毙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工耙蔑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留见妒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓甸陌,卻偏偏與公主長(zhǎng)得像须揣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钱豁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果耻卡,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫(huà)全貌牲尺。在這里你可以看...
    每天刷兩次牙閱讀 8,495評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果卵酪,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫(huà)全貌谤碳。在這里你可以看...
    F麥子閱讀 5,113評(píng)論 5 13
  • 書(shū)寫(xiě)的很好,翻譯的也棒!感謝譯者夯缺,感謝感謝府阀! iOS-Core-Animation-Advanced-Techni...
    錢(qián)噓噓閱讀 2,298評(píng)論 0 6
  • 在iOS實(shí)際開(kāi)發(fā)中常用的動(dòng)畫(huà)無(wú)非是以下四種:UIView動(dòng)畫(huà),核心動(dòng)畫(huà)臭蚁,幀動(dòng)畫(huà)最铁,自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)。 1.UIView...
    請(qǐng)叫我周小帥閱讀 3,101評(píng)論 1 23
  • 來(lái)簡(jiǎn)書(shū)幾個(gè)月了,堅(jiān)持認(rèn)真更文卻才三周系枪,而且是在加入365挑戰(zhàn)營(yíng)以后雀哨。 說(shuō)實(shí)話,寫(xiě)到今天都有些泄氣了。自己那水平也叫...
    蕓窗月影閱讀 727評(píng)論 6 9