翻譯自raywenderlich網站iOS教程Graphics & Animation系列git
準備開始github
首先用storyboard佈局一個頁面(或者你能夠用純代碼去設置),效果以下:swift
在文件中聲明和拖拽出以下參數:(blueSquare、redSquare、imgView是storyboard拖出的)dom
// x:80 y: 420 width: 8 height: 8 bgColor:blue @IBOutlet weak var blueSquare: UIView! // x:156 y: 219 width: 8 height: 8 bgColor:red @IBOutlet weak var redSquare: UIView! // x:33 y: 137 width: 254 height: 172 @IBOutlet weak var imgView: UIImageView! private var originalBounds = CGRect.zero private var originalCenter = CGPoint.zero private var animator: UIDynamicAnimator! private var attachmentBehavior: UIAttachmentBehavior! private var pushBehavior: UIPushBehavior! private var itemBehaviod: UIDynamicItemBehavior!
紅色和藍色方塊表示讓圖片作動畫的UIKit動態物理引擎點:藍色方塊表示觸摸開始的位置,紅色方塊會在手指移動時跟蹤。async
如今給view添加一個手勢識別器:在DynamicsTossingVC.swift
添加以下代碼:ide
@IBAction func handleAttachmentGesture(_ sender: UIPanGestureRecognizer) { let location = sender.location(in: self.view) let boxLocation = sender.location(in: self.imgView) switch sender.state { case .began: print("Touch start position is \(location)") print("Start location in image is \(boxLocation)") case .ended: print("Touch end position is \(location)") print("End location in image is \(boxLocation)") default: break } }
你能夠在storyboard中添加Pan Gesture,也能夠用代碼建立一個panGesture,並關聯這個方法。
如今運行項目,在屏幕上滑動或者拖動,控制檯的輸出信息應該以下相似:工具
Touch start position is (234.666656494141, 463.666656494141) Start location in image is (201.666656494141, 306.666656494141) Touch end position is (63.0, 482.666656494141) End location in image is (30.0, 325.666656494141)
如今咱們已經設置了基本的界面,下面開始讓它動起來。佈局
UIDynamicAnimator和UIAttachmentBehavior動畫
如今咱們想要作的第一件事就是讓imgView在拖動的時候移動,將要用到一種名爲UIAttachmentBehavior的UIKit Dynamics類來執行此操做。網站
打開DynamicsTossingVC.swift
並將如下代碼放在viewDidLoad()
中super.viewDidLoad()
下方。
animator = UIDynamicAnimator(referenceView: view) originalBounds = imgView.bounds originalCenter = imgView.center
上面的代碼創建了一個UIDynamicAnimator,它是UIKit基於物理動畫的引擎。 咱們用VC的view做爲參考視圖,該視圖定義了動畫製做者的座標系統。
能夠將動畫添加到動畫製做工具中,這樣能夠執行諸如附加視圖,推進視圖,使其受重力影響等等。
從UIAttachmentBehavior開始,使圖像視圖在製做平移手勢時跟蹤手指。
爲此,請將如下代碼添加到handleAttachmentGesture(sender :)
下面case .began
:部分的兩個print語句下方:
// 刪除可能存在的任何現有動畫行爲。 animator.removeAllBehaviors() // 建立一個UIAttachmentBehavior,它將圖像視圖中的點附加到用戶點擊一個錨點(碰巧是徹底相同的點)。 稍後,更改定位點使圖像視圖移動。 // 將錨點附加到視圖就像安裝一個將錨點鏈接到視圖上的固定附件位置的不可見杆。 let centerOffset = UIOffset(horizontal: boxLocation.x - imgView.bounds.midX, vertical: boxLocation.y - imgView.bounds.midY) attachmentBehavior = UIAttachmentBehavior(item: imgView, offsetFromCenter: centerOffset, attachedToAnchor: location) // 更新紅色方塊以指示定位點,並使用藍色方塊來指示圖像視圖內所附的點。 當手勢開始時,這些將是相同的點。 redSquare.center = attachmentBehavior.anchorPoint blueSquare.center = location // 將此行爲添加到動畫器以使其生效。 animator.addBehavior(attachmentBehavior)
接下來,須要讓錨點跟隨手指。 在handleAttachmentGesture_ :)
中,用下面的代碼替換default下的break語句:
attachmentBehavior.anchorPoint = sender.location(in: view) redSquare.center = attachmentBehavior.anchorPoint
default
下, 這裏的代碼簡單地將錨點和紅色方塊與手指的當前位置對齊。 當用戶的手指移動時,手勢識別器調用此方法更新錨點以跟隨觸摸。 另外,animator 會自動更新視圖以跟隨定位點。
運行demo,拖動視圖會出現以下效果:
注意視圖不單單是在屏幕上進行旋轉; 若是您在圖像的某個角落開始手勢,則因爲錨點的緣故,視圖會隨着手指移動而旋轉。
可是,當完成拖動時,將視圖恢復到原始位置會更好。 爲了解決這個問題,將這個新方法添加到類中:
fileprivate func resetDemo() { animator.removeAllBehaviors() UIView.animate(withDuration: 0.5) { self.imgView.bounds = self.originalBounds self.imgView.center = self.originalCenter self.imgView.transform = CGAffineTransform.identity } }
而後在handleAttachmentGesture(_:)
中的case .ended下
面兩個print後面調用:
resetDemo()
運行demo。如今拖動圖像後,它應該恢復到原始位置。
UIPushBehavior
接下來,咱們須要在中止拖動時分離視圖,併爲其提供動力,以便在運動中釋放視圖時能夠繼續其軌跡。 將使用UIPushBehavior完成此操做。
首先,須要兩個常量。 將這些添加到文件的頂部:
let ThrowingThreshold: CGFloat = 1000 let ThrowingVelocityPadding: CGFloat = 35
ThrowingThreshhold表示視圖必須移動的速度有多快才能使視圖繼續移動(而不是當即返回到原始位置)。 ThrowingVelocityPadding是一個常數,它會影響投擲應該多快或多慢(這是經過反覆試驗來選擇的)。
最後,在handleAttachmentGesture(_ :)
內部,用下面的代碼替換resetDemo()
的調用
animator.removeAllBehaviors() // 1 let velocity = sender.velocity(in: view) let magnitude = sqrt(velocity.x * velocity.x + velocity.y * velocity.y) if magnitude > ThrowingThreshold { // 2 let pushBehavior = UIPushBehavior(items: [imgView], mode: .instantaneous) pushBehavior.pushDirection = CGVector(dx: velocity.x / 10, dy: velocity.y / 10) pushBehavior.magnitude = magnitude / ThrowingVelocityPadding self.pushBehavior = pushBehavior animator.addBehavior(pushBehavior) // 3 let angle = Int(arc4random_uniform(20)) - 10 itemBehavior = UIDynamicItemBehavior(items: [imgView]) itemBehavior.friction = 0.2 itemBehavior.allowsRotation = true itemBehavior.addAngularVelocity(CGFloat(angle), for: imgView) animator.addBehavior(itemBehavior) // 4 let timeOffset = Int(0.4 * Double(NSEC_PER_SEC)) DispatchQueue.main.asyncAfter(deadline: DispatchTime .now() + DispatchTimeInterval.seconds(timeOffset)) { self.resetDemo() } } else { resetDemo() }
對上面的代碼一節一節地回顧一下:
一、獲取手勢的拖動速度。計算速度的大小 - 這是由x方向速度和y方向速度造成的三角
形的斜邊。
要理解這個背後的理論,請查看這個Trigonometry for Game Programming教程。
二、假設手勢速度超過爲動做設置的最小閾值,則設置push行爲。 所需的方向由x和y速度組成,並轉換爲一個給定方向部分的向量。 一旦設置了推送行爲,就將其添加到動畫序列中。
三、本部分設置了一些旋轉以使圖像「飛走」。 在這裏閱讀複雜的計算。 其中一些取決於手指在啓動手勢時距離手指邊緣的距離。
調整這塊的value,觀察運動如何改變效果。
四、在指定的時間間隔以後,動畫經過將圖像發送回目的地進行重置,因此它會縮回並返回到屏幕 - 就像球從牆上彈起同樣
運行能夠看到以下效果:
這裏是最終的demo。此demo是raywenderlich下面iOS的Graphics & Animation整個教程系列的集合。