[swift 3]iOS10下使用UIViewPropertyAnimator寫動畫

在iOS 10中帶入了一種“全新的,面向對象的,完全可交互和停止的動畫。你可以掌控你的動畫并且和手勢操作相關聯(lián)”。

簡單點說,就是在iOS 10下開發(fā)者可以更加容易的取消、反轉、暫停、重啟動畫,并且可以修改動畫的時序和執(zhí)行時間。而且以上功能在view controller轉場動畫上也可以使用。

開始項目

首先,準備好我們要做動畫的視圖。創(chuàng)建一個新的項目,只是用默認的配置即可。

import UIKit

class ViewController: UIViewController {

  var circleCenter: CGPoint!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
    circle.center = self.view.center
    circle.layer.cornerRadius = 50.0
    circle.backgroundColor = UIColor.green
    
    circle.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragCircle)))

    self.view.addSubview(circle)
  }

  func dragCircle(gesture: UIPanGestureRecognizer) {
    let target = gesture.view!
    
    switch gesture.state {
    case .began, .ended:
      circleCenter = target.center
    case .changed:
      let translation = gesture.translation(in: self.view)
      target.center = CGPoint(x: circleCenter.x + translation.x,
                              y: circleCenter.y + translation.y)
    default:break
    }
  }

沒什么復雜的。只是在viewDidLoad方法里創(chuàng)建了一個綠色的圓形,并放在了屏幕的中央。然后在上面附加了一個UIPanGestureRecognizer實例。這樣圓形就可以相應用戶的拖拽手勢了。響應手勢的方法是dragCircle。

關于UIViewPropertyAnimator

UIViewPropertyAnimator是實現(xiàn)view屬性動畫主要用到的類。UIViewPropertyAnimator實例維護著一組動畫,并且新的動畫基本上任何時候都再添加進去。注意:以后凡是說到這個類的實例就叫做animator。

如果有兩個以上的動畫修改view的同一個屬性,那么最后一個添加或者開始的起作用。最有意思的是,animator添加了新動畫之后不是一下轉到新動畫上,而是有一個過渡的過程。

可中斷,反轉的動畫

現(xiàn)在我們就來開發(fā)一個動畫。用戶拖動的時候圓形逐漸變大為原來size的兩倍,松開之后復原。

首先給view controller添加兩個屬性:

class ViewController: UIViewController {

  var circleCenter: CGPoint!
  
  var circleAnimator: UIViewPropertyAnimator!
  let animationDuration = 4.0

viewDidLoad方法里初始化animator:

    circleAnimator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeOut, animations: {[unowned circle] in
        // 2x scale
        circle.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
    })

我們在初始化circleAniamtor的時候,傳入了兩個參數(shù)durationcurve。curve可以參數(shù)可以選擇系統(tǒng)預定義的四種類型之一,我們這里選擇了.easeInOut。其他的選項是easeIn、easeOut、和linear。最后出入的參數(shù)是一個描述動畫的閉包。

現(xiàn)在,我們需要在拖動方法里觸發(fā)動畫。

  func dragCircle(gesture: UIPanGestureRecognizer) {
    let target = gesture.view!
    
    switch gesture.state {
    case .began, .ended:
      circleCenter = target.center
      
      if(circleAnimator.isRunning) {
        circleAnimator.pauseAnimation()
        circleAnimator.isReversed = gesture.state == .ended
      }
      circleAnimator.startAnimation()
    case .changed:
      let translation = gesture.translation(in: self.view)
      target.center = CGPoint(x: circleCenter.x + translation.x,
                              y: circleCenter.y + translation.y)
    default:break
    }
  }

運行代碼之后,動畫在拖動的時候可以運行了。

但是,一次動畫執(zhí)行完成之后再次拖動的時候APP就會崩潰了。這是為什么呢?

動畫在執(zhí)行依次,animator的引用就會被廢棄。Animator有一下的三個狀態(tài):

  1. inactive,初始狀態(tài),動畫執(zhí)行完成之后也會回到這個狀態(tài)。
  2. active,動畫執(zhí)行的時候的狀態(tài)。
  3. stopped,調(diào)用stopAnimation方法之后進入這個狀態(tài)。

三個狀態(tài)的轉化見下圖:

state

只要動畫進入了Inactive,animator的動畫就會被廢棄,包括animator的completion閉包。

我們已經(jīng)見過了startAnimation方法,現(xiàn)在來看看其他的幾個改變動畫狀態(tài)的方法。

首先修改circleAnimator的初始化方法:

circleAnimator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeOut)

修改dragCircle方法:

      if circleAnimator.state == .active {
        // set to inactive state
        circleAnimator.stopAnimation(true)
      }
      
      if gesture.state == .began {
        circleAnimator.addAnimations {
          target.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
        }
      } else {
        circleAnimator.addAnimations {
          target.transform = CGAffineTransform.identity
        }
      }

// ...

現(xiàn)在,無論用戶合適停止拖拽,動畫清空都不會影響下一次的動畫執(zhí)行。因為在下次拖拽的時候會重新添加動畫。

注意:stopAnimation(true)等于:

  circleAnimator.stopAnimation(false)
  circleAnimator.finishAnimation(at: .current)

stopAnimation(false)不結束,動畫保持在“停滯狀態(tài)”。finishAnimation(at: .current)把view的屬性設置為動畫當前的屬性值。設置view的屬性的值來自UIViewAnimatingPosition的值。如果傳入的是.start或者.end那么圓形會立刻變成動畫開始時或者結束時的樣子。

動畫時間

這個動畫在執(zhí)行的時候還有一個問題。那就是不管圓形的動畫值停留在了哪個值上,下次拖拽的時候動畫還是要執(zhí)行4秒鐘的事件。顯然,有保留值的時候只需要執(zhí)行剩下的值所需要的時間。我們來改進一下:

  case .began, .ended:
      circleCenter = target.center
      
      let durationFactor = circleAnimator.fractionComplete
      circleAnimator.stopAnimation(false)
      circleAnimator.finishAnimation(at: .current)
      
      if gesture.state == .began {
        circleAnimator.addAnimations {
          target.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
        }
      } else {
        circleAnimator.addAnimations {
          target.transform = CGAffineTransform.identity
        }
      }
      
      circleAnimator.startAnimation()
      circleAnimator.pauseAnimation()
      circleAnimator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor)
      
    case .changed:
// ...

現(xiàn)在我們顯示的停止動畫,根據(jù)方向添加動畫,最后重啟animator。使用circleAnimator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor)方法調(diào)整剩余的時間。這個方法也可以用來調(diào)整animator的動畫時間方法。如果給參數(shù)withTimingParameters傳入一個新的時間方法,動畫的計時方式會平滑的從老的方式過渡到新的方式。

最后

UIViewAnimationCurve是上面我們用的計時方法。但是還有兩個新的計時對象UISpringTimingParametersUICubicTimingParameters。使用者兩個計時方法可以開發(fā)出更加酷炫的動畫來,稍后我們專門介紹。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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