Swift 4 動(dòng)畫 - 1. UIView Animations

所有示例代碼均可以在 Animations-Demo 下載到

iOS 中實(shí)現(xiàn)動(dòng)畫有好幾種方式,UIView 無疑是最簡單的一種,但是所有的動(dòng)畫歸根結(jié)底還是 layer 層的動(dòng)畫。UIView 層面的動(dòng)畫只是對(duì) layer 層部分屬性的封裝。我們可以直接對(duì) UIView 的 alpha 、boundscenter 、frame、transformbackgroundColor(如果view沒有實(shí)現(xiàn)draw(_:))。上面這些屬性看起來不多,但是足夠滿足大部分日常開發(fā)動(dòng)畫。

在 UIView 中執(zhí)行一個(gè)動(dòng)畫非常簡單,系統(tǒng)已經(jīng)幫我們封裝好了一切,你只需要將你想要?jiǎng)赢嫷膶傩苑诺?animations 的閉包中即可。

UIView.animate(withDuration: 0.4) {
  self.v.backgroundColor = UIColor.red
}

閉包中可以同時(shí)執(zhí)行多個(gè)屬性的動(dòng)畫,也可以是多個(gè)view的動(dòng)畫

UIView.animate(withDuration: 0.4) {
  self.v.backgroundColor = UIColor.red
  self.v.center.y += 100
  self.v2.alpha = 0
}

如果我們想讓一個(gè)屬性在 animations 閉包中執(zhí)行,但是又不要執(zhí)行動(dòng)畫,可以這樣。

UIView.animate(withDuration: 0.4) {
  self.v.backgroundColor = UIColor.red
  UIView.performWithoutAnimation {
    self.v2.alpha = 0
  }
}

放在 performWithoutAnimation 閉包中就會(huì)不執(zhí)行動(dòng)畫了, 這個(gè)在有時(shí)候做項(xiàng)目的時(shí)候某個(gè)功能總是會(huì)莫名其妙的調(diào)一下或者執(zhí)行一個(gè)很奇怪的動(dòng)畫,這時(shí)候可以把那段 code 放在這個(gè)閉包中,就不會(huì)有動(dòng)畫了。

iOS 9 開始 , UIVisualEffectVieweffect 屬性也是可以動(dòng)畫的。動(dòng)畫前設(shè)置為 nil ,在animations: 的block 中設(shè)置 effect

let effect = UIBlurEffect(style: .dark)
effectView.effect = nil
UIView.animate(withDuration: 1) {
  self.effectView.effect = effect
}
示例

UIView 動(dòng)畫 options

UIView 動(dòng)畫比較完整的版本并不是上面那么簡短,還有很多其他的參數(shù)可以配置

animate(withDuration:, delay: , options: , animations: , completion: )
  • withDuration:動(dòng)畫的持續(xù)時(shí)間,也可理解為動(dòng)畫的執(zhí)行速度,持續(xù)時(shí)間越小速度越快
  • delay:動(dòng)畫開始之前的延時(shí),默認(rèn)是無延時(shí)。
  • options:一個(gè)附加選項(xiàng),UIViewAnimationOptions 可以指定多個(gè)
  • animations:執(zhí)行動(dòng)畫的閉包
  • completion:動(dòng)畫完成后執(zhí)行的閉包,可以為nil,可以在這里鏈接下一個(gè)動(dòng)畫。

下面是一些主要的options (UIViewAnimationOptions) :

  • 動(dòng)畫執(zhí)行對(duì)應(yīng)的曲線(緩沖): 動(dòng)畫執(zhí)行過程速度的改變,會(huì)有一個(gè)加速度或者一個(gè)減速度。
    • .curveEaseIn
    • .curveEaseOut
    • .curveEaseInOut
    • .curveLinear
  • .repeat : 指定這個(gè)選項(xiàng)后,動(dòng)畫會(huì)無限重復(fù)
  • .autoreverse:往返動(dòng)畫,從開始執(zhí)行到結(jié)束后,又從結(jié)束返回開始。

但是這里會(huì)有個(gè)問題,如果我們讓一個(gè)view 移動(dòng)100pt。使用 .autoreverse , 代碼如下

let opts = UIViewAnimationOptions.autoreverse
UIView.animate(withDuration: 1, delay: 0, options: opts, animations: {
  self.view2.center.x -= 100
}, completion: nil)
示例

發(fā)現(xiàn)很順利的往返之后,又跳了一下,是因?yàn)槲覀僾iew2的center 其實(shí)已經(jīng)改變了。如果想讓它回到原位,只需要在完成時(shí)候指定它的位置即可

let xorig = self.view2.center.x
let opts = UIViewAnimationOptions.autoreverse
UIView.animate(withDuration: 1, delay: 0, options: opts, animations: {
  self.view2.center.x -= 100
}, completion: { _ in
  self.view2.center.x = xorig
})
示例

如果想讓這個(gè)動(dòng)畫無限次循環(huán),只需要加一個(gè)option

let opts: UIViewAnimationOptions = [.autoreverse , .repeat]

如果需要執(zhí)定循環(huán)次數(shù)

let xorig = self.view2.center.x
let opts: UIViewAnimationOptions = UIViewAnimationOptions.autoreverse
UIView.animate(withDuration: 1, delay: 0, options: opts, animations: {
  UIView.setAnimationRepeatCount(5)
  self.view2.center.x -= 100
}, completion: { _ in
  self.view2.center.x = xorig
})

這樣就會(huì)只循環(huán)五次

還有一些options指定如果另一個(gè)動(dòng)畫已經(jīng)作用在這個(gè)view上時(shí),該怎么辦。

  • .beginFromCurrentState 從上次動(dòng)畫的當(dāng)前狀態(tài)繼續(xù)這次的動(dòng)畫,立即執(zhí)行上次動(dòng)畫的完成b閉包。會(huì)使用 presentation layer 決定從哪里開始。如果可能的化,會(huì)混合兩次的動(dòng)畫。
  • .overrideInheritedDuration 不繼承別的動(dòng)畫的持續(xù)時(shí)間(默認(rèn)是繼承)
  • .overrideInheritedCurve 不繼承別的動(dòng)畫的曲線(默認(rèn)是繼承)

iOS 8之后.beginFromCurrentState 就很少用到了,在 iOS 7 時(shí)候下面的動(dòng)畫是會(huì)跳一下,現(xiàn)在都很平滑了

UIView.animate(withDuration: 0.5) {
  self.view3.center.x -= 100
}
UIView.animate(withDuration: 0.5) {
  self.view3.center.y += 100
}
示例

取消view的動(dòng)畫

一旦一個(gè)動(dòng)畫開始執(zhí)行,在執(zhí)行的過程中我們?cè)趺慈∠??下面有一個(gè)執(zhí)行時(shí)間很長的動(dòng)畫。

self.pOrig = self.view4.center
self.pFinal = self.view4.center
self.pFinal.x -= 100
UIView.animate(withDuration: 4) {
  self.view4.center = self.pFinal
}

這個(gè)動(dòng)畫執(zhí)行很漫長,我中途想取消怎么辦 ?

  • 調(diào)用 layer 層的 removeAllAnimations
self.view4.layer.removeAllAnimations()
示例

[圖片上傳中...(pic_02.gif-92d0a1-1511337092782-0)]

這種方式會(huì)跳動(dòng)一下,很不好。

  • 我們可以記錄用一個(gè)0.1秒的動(dòng)畫到終點(diǎn),先拿到當(dāng)前的位置。
self.view4.layer.position = self.view4.layer.presentation()!.position
self.view4.layer.removeAllAnimations()
UIView.animate(withDuration: 0.1) {
  self.view4.center = self.pFinal
}
示例

這樣看起來 smooth 多了。

transform

view的 transform 非常簡單,也比較常用,就旋轉(zhuǎn)平移縮放,可以疊加在一起使用。

UIView.animate(withDuration: 1.2) {
  self.view5.transform = CGAffineTransform.identity
    .translatedBy(x: -100, y: 0)
    .rotated(by:CGFloat(Double.pi/4))
    .scaledBy(x: 0.5, y: 0.5)
}
示例

如果需要回到原來的位置 用 self.view5.transform = CGAffineTransform.identity 即可

自定義 animation property

我們可以在自己自定義的view上自定義一個(gè)可以動(dòng)畫的屬性。例如加一個(gè)swing 屬性,設(shè)置為true 則 center.x 加 100 , false 則 center.x 減 100

class MyView: UIView {
  var swing: Bool = true {
    didSet{
      var p = self.center
      p.x = self.swing ? p.x + 100 : p.x - 100
      UIView.animate(withDuration: 0) {
        self.center = p
      }
    }
  }
}

然后在動(dòng)畫的時(shí)候只需要使用swing屬性就可以了

UIView.animate(withDuration: 0.4) {
  self.view1.swing = !self.view1.swing
}
示例

Spring 彈性動(dòng)畫

彈性動(dòng)畫一般有一個(gè)很快的初速度,在結(jié)束的時(shí)候也會(huì)有一個(gè)擺動(dòng)。類似彈簧的效果。

UIView.animate(withDuration: 1 , delay: 0 , usingSpringWithDamping: 0.3 , initialSpringVelocity: 8 , options: [] , animations: {
  self.view2.center.x -= 100
}, completion: nil)
示例

usingSpringWithDamping 小于1 ,動(dòng)畫在最終位置都會(huì)有一個(gè)搖擺。值越小搖晃的越緩和。initialSpringVelocity 表示一個(gè)初始速度, 動(dòng)畫執(zhí)行快慢由他和duration共同決定。這個(gè)需要根絕實(shí)際情況多多調(diào)試。

Keyframe 關(guān)鍵幀動(dòng)畫

什么意思呢?就是我們可以把動(dòng)畫分成一個(gè)一個(gè)小的階段,然后在將這些結(jié)合在一起。使用 UIView.animateKeyframes(withDuration:,delay:,options:,animations:,completion:) 然后在 animations 的閉包中調(diào)用 UIView.addKeyframe(withRelativeStartTime: , relativeDuration: , animations:) 添加關(guān)鍵幀 。可以多次調(diào)用指定多個(gè)點(diǎn)。 startTime 是從0 - 1 的相對(duì)時(shí)間 ,相對(duì)于整體動(dòng)畫的時(shí)間??磦€(gè)例子 。

var p = self.view3.center
let dur = 0.25
var start = 0.0
let dx: CGFloat = -100
let dy: CGFloat = 50
var dir: CGFloat = 1

UIView.animateKeyframes(withDuration: 4, delay: 0, options: [], animations: {
  UIView.addKeyframe(withRelativeStartTime: start , relativeDuration: dur , animations: {
    p.x += dx*dir
    p.y += dy
    self.view3.center = p
  })
  start += dur
  dir *= -1
  UIView.addKeyframe(withRelativeStartTime: start , relativeDuration: dur , animations: {
    p.x += dx*dir
    p.y += dy
    self.view3.center = p
  })
  start += dur
  dir *= -1
  UIView.addKeyframe(withRelativeStartTime: start , relativeDuration: dur , animations: {
    p.x += dx*dir
    p.y += dy
    self.view3.center = p
  })
  start += dur
  dir *= -1
  UIView.addKeyframe(withRelativeStartTime: start , relativeDuration: dur , animations: {
    p.x += dx*dir
    p.y += dy
    self.view3.center = p
  })
}, completion: nil)
示例

上面的示例,一共有四個(gè)關(guān)鍵幀。每個(gè)relativeDuration 0.25, 表示占總時(shí)長的1/4。四段等時(shí)長的幀動(dòng)畫。我們上面并沒有指定options,這里使用的是UIViewKeyframeAnimationOptions,默認(rèn)是 .calculationModeLinear . 當(dāng)然我們也可以指定其他的option,這個(gè)作用可以自己去嘗試。我們的關(guān)鍵幀動(dòng)畫的必報(bào)里也是可以指定多個(gè)屬性、或者多個(gè)view的動(dòng)畫。

Transitions 過渡

過度動(dòng)畫強(qiáng)調(diào)的是view改變內(nèi)容。一般有兩個(gè)方法

  • UIView.transition(with:, duration:, options:, animations:, completion:)
  • UIView.transition(from: , to:, duration:, options:, completion:)

過渡動(dòng)畫的類型是一個(gè)options (UIViewAnimationOptions

  • .transitionFlipFromLeft,.transitionFlipFromRight
  • .transitionFlipFromTop,.transitionFlipFromBottom
  • .transitionCurlUp,.transitionCurlDown
  • .transitionCrossDissolve

來看一個(gè)例子,我們來修改下 UIImageView 的內(nèi)容

UIView.transition(with: self.imageView , duration: 0.6 , options: .transitionFlipFromLeft , animations: {
  if self.imageView.image == #imageLiteral(resourceName: "rabbit") {
    self.imageView.image = #imageLiteral(resourceName: "elephant")
  }else{
    self.imageView.image = #imageLiteral(resourceName: "rabbit")
  }
}, completion: nil)

從swift 3 開始 圖片對(duì)象只要在資源里能找到都可以直接輸出來,這里copy過來顯示的是#imageLiteral(resourceName: "rabbit")
實(shí)際是這樣的

示例

運(yùn)行效果:

示例

我們也可以對(duì)自定義view的draw rect 最transition。

class MyView1: UIView {
  var reverse = false
  override func draw(_ rect: CGRect) {
    let f = self.bounds.insetBy(dx: 10, dy: 10)
    let context = UIGraphicsGetCurrentContext()
    if self.reverse {
      context?.strokeEllipse(in: f)
    }else{
      context?.stroke(f)
    }
  }
}

動(dòng)畫部分只需要重新繪制即可。

self.view4.reverse = !self.view4.reverse
UIView.transition(with: self.view4 , duration: 0.6 , options: .transitionFlipFromLeft , animations: {
  self.view4.setNeedsDisplay()
}, completion: nil)
示例

默認(rèn)情況下,一個(gè)視圖的子視圖在transition動(dòng)畫期間改變layout,這個(gè)改變是不會(huì)有動(dòng)畫的,將在transition結(jié)束的時(shí)候直接改變,如果想要做動(dòng)畫改變,需要加上.allowAnimatedContent option

UIView.transition(from: , to:, duration:, options:, completion:) 這個(gè)方法需要兩個(gè)view ,第一個(gè)會(huì)被第二個(gè)替換掉。整個(gè)transition動(dòng)畫會(huì)在他們的superview進(jìn)行動(dòng)畫。有兩種可能的情況。

  • 刪除一個(gè)subview , 添加另一個(gè)
    如果.showHideTransitionViews 不包含在 options 中 ,那么第二個(gè)view在我們開始動(dòng)畫時(shí),并不在視圖層級(jí)。transition動(dòng)畫 remove將第一個(gè)view從superview上remove掉,將第二個(gè)view添加到相同的superview上。

  • 隱藏一個(gè)subview , 顯示另一個(gè)
    如果.showHideTransitionViews 包含在options 中 , 那么兩個(gè)subview一開始都在視圖層級(jí)中。第一個(gè)view的isHidden是false , 第二個(gè)為true 。 transition動(dòng)畫會(huì)對(duì)調(diào)兩個(gè)view的isHidden屬性。

let lab2 = UILabel(frame: self.label1.frame)
lab2.text = self.label1.text == "Hello" ? "World" : "Hello"
lab2.textColor = UIColor.white
lab2.sizeToFit()
UIView.transition(from: self.label1 , to: lab2 , duration: 0.8 , options: .transitionFlipFromLeft , completion: { _ in
  self.label1 = lab2
})
示例

ImageView 和 Image 動(dòng)畫

UIImageView 上執(zhí)行動(dòng)畫非常簡單,只需要提供animationImages 屬性。一個(gè)UIImage 數(shù)組。這個(gè)數(shù)組代碼一個(gè)一個(gè)的幀,當(dāng)我們調(diào)用 startAnimating 方法的時(shí)候,這個(gè)數(shù)組的圖片就會(huì)輪流播放。animationDuration 決定了播放的速度。animationRepeatCount指定重復(fù)次數(shù) (默認(rèn)是0 , 代表無限重復(fù)),或者調(diào)用stopAnimating 方法停止動(dòng)畫。

例子 :

let rabbit = #imageLiteral(resourceName: "rabbit")
UIGraphicsBeginImageContextWithOptions(rabbit.size , false, 0)
let empty = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let arr = [rabbit,empty,rabbit,empty,rabbit]
imageView.animationImages = arr
imageView.animationDuration = 2
imageView.animationRepeatCount = 3
imageView.startAnimating()
示例

UIImage 有一些類方法為 UIImageView 構(gòu)造 可以動(dòng)畫的image :

  • UIImage.animatedImage(with:, duration:)
    直接指定了image數(shù)組和duration。
  • UIImage.animatedImageNamed(, duration: )
    提供一個(gè)單個(gè)的image name , 系統(tǒng)會(huì)自動(dòng)在后面加 "0" (如果失敗則"1") 。使這個(gè)image成為第一個(gè)image。最后一位數(shù)字累加。(知道沒有圖片或者到達(dá)”1024“)
  • UIImage.animatedResizableImageNamed(, capInsets: , duration: )
    跟上面的方式差不多,但是同時(shí)對(duì)每個(gè)image做了拉伸或者平鋪。 圖像本身也有resizableImage(withCapInsets: , resizingMode: )方法可以縮放(指定某個(gè)區(qū)域的拉伸或者平鋪)
let im = UIImage.animatedImageNamed("voice", duration: 2)
imageView2.image = im

其中voice1-3 已經(jīng)命名好,放在Assets.xcassets

示例

我們?cè)僭谝粋€(gè) button上畫一個(gè)圓從大到小的動(dòng)畫

var arr = [UIImage]()
let w : CGFloat = 18
for i in 0 ..< 6 {
  UIGraphicsBeginImageContextWithOptions(CGSize(width: w, height: w), false, 0)
  let context = UIGraphicsGetCurrentContext()!
  context.setFillColor(UIColor.red.cgColor)
  let ii = CGFloat(i)
  let rect = CGRect(x: ii, y:ii, width: w-ii*2, height: w-ii*2)
  context.addEllipse(in: rect)
  context.fillPath()
  let im = UIGraphicsGetImageFromCurrentImageContext()!
  UIGraphicsEndImageContext()
  arr.append(im)
}
let im = UIImage.animatedImage(with: arr, duration: 0.5)
self.button.setImage(im, for: .normal)
示例
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,686評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,258評(píng)論 5 13
  • 先看看CAAnimation動(dòng)畫的繼承結(jié)構(gòu) CAAnimation{ CAPropertyAnimation { ...
    時(shí)間不會(huì)倒著走閱讀 1,795評(píng)論 0 1
  • 在iOS實(shí)際開發(fā)中常用的動(dòng)畫無非是以下四種:UIView動(dòng)畫,核心動(dòng)畫,幀動(dòng)畫,自定義轉(zhuǎn)場動(dòng)畫。 1.UIView...
    請(qǐng)叫我周小帥閱讀 3,313評(píng)論 1 23
  • 2UIView動(dòng)畫 2.1概述 UIView視圖的動(dòng)畫功能,可以使在更新或切換視圖時(shí)有放緩節(jié)奏、產(chǎn)生流暢的動(dòng)畫效果...
    Kevin_Junbaozi閱讀 1,739評(píng)論 0 0

友情鏈接更多精彩內(nèi)容