App中使用適當的動畫效果可以提高使用者體驗,最近在研究幾種常見的轉場動畫(View Controller Transition),今天想來分享一下這種「展開式」的轉場動畫是如何實現的。
展開式轉場動畫
在iOS 7 之後開始支持Custom Transition,以上面這張GIF為例,其中包含了兩種轉場動畫。
- 當使用者點開其中一張圖片的時候,就會將畫面進行上下分離並且平移的動畫(此時做的是navigation push的動作)。
- 當使用者點了返回按鈕,會將原先分離的上下部份進行合併的動畫(此時做的是navigation pop的動作)。
接下來分享下具體實現的方法。
兩個畫面
- HomeViewController,內容為多張圖片組成的TableView
- DetailViewController,以一張大圖填滿畫面。
當我們在Home點了某一個Cell,就會跳到Detail中並且顯示這張圖。
設定自定義轉場動畫
我們建立了一個名稱為SKExpandTransition的class,該class會去實現Protocol UIViewControllerAnimatedTransitioning中規定的兩個方法
1 2 3 4 5 |
// 轉場時間 func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval // 具體的轉場動畫 func animateTransition(using transitionContext: UIViewControllerContextTransitioning) |
在HomeViewController中實現UINavigationControllerDelegate中的一個方法,我們可以在Navigation進行push或者pop的時候,提交自己的transition方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
extension HomeViewController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == UINavigationControllerOperation.push { transition.operation = UINavigationControllerOperation.push transition.duration = 1 transition.selectedFrame = self.selectedCellFrame return transition } if operation == UINavigationControllerOperation.pop { transition.operation = UINavigationControllerOperation.pop transition.duration = 1 return transition } return nil } } |
動畫實現的主要部分
其實push過去和pop回來的動畫邏輯是類似的,我們的目標就是如何去抓取上下分離的部分,然後做一個平移的動畫。
我們先來抓取上下兩個部份的圖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
if operation == .push { // 抓一張SourceView的圖(HomeViewController) UIGraphicsBeginImageContextWithOptions((sourceView?.bounds.size)!, true, 1) sourceView?.drawHierarchy(in: bounds, afterScreenUpdates: false) snapShot = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() // SnapShot的圖 let tempImageRef = snapShot.cgImage! // selectedFrame是使用者所點選cell的屬性frame let topHeight = selectedFrame.origin.y let imageViewTopFrame = CGRect(x: 0, y: 0, width: bounds.width, height: topHeight) let imageViewBottomFrame = CGRect(x: 0, y: topHeight, width: bounds.width, height: bounds.height - selectedFrame.origin.y) // 裁剪圖片 let topImageRef = tempImageRef.cropping(to: imageViewTopFrame) let bottomImageRef = tempImageRef.cropping(to: imageViewBottomFrame) // 上半部的圖片 if topImageRef != nil { imageViewTop = UIImageView(image: UIImage(cgImage: topImageRef!, scale: snapShot.scale, orientation: UIImageOrientation.up)) imageViewTop?.frame = imageViewTopFrame } if (bottomImageRef != nil) { // 下半部的圖片 imageViewBottom = UIImageView(image: UIImage(cgImage: bottomImageRef!, scale: snapShot.scale, orientation: UIImageOrientation.up)) imageViewBottom!.frame = imageViewBottomFrame } } |
接著通過UIAnimation來實現上下圖的平移動畫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// animation if self.operation == .push { container.addSubview(backgroundView) container.addSubview(destinationView!) container.addSubview(self.imageViewTop!) container.addSubview(self.imageViewBottom!) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { () -> Void in self.imageViewTop!.frame = CGRect(x: 0, y: -self.imageViewTop!.frame.height, width: self.imageViewTop!.frame.width, height: self.imageViewTop!.frame.height) self.imageViewBottom!.frame = CGRect(x: 0, y: bounds.height, width: self.imageViewBottom!.frame.width, height: self.imageViewBottom!.frame.height) destinationView?.alpha = 1 }, completion: { (finish) -> Void in self.imageViewTop?.removeFromSuperview() self.imageViewBottom?.removeFromSuperview() transitionContext.completeTransition(true) }) } |
在進行push的時候,我們就會將截圖後的內容存起來(imageViewTop, imageViewBottom)
這樣在進行pop的時候,就可以直接拿這兩張圖來做還原的動畫(從上下外圍向中心移動)
進一步的閱讀:
- 為了方便進一步的了解,可以到Github上上面提到「展開式」轉場動畫的例子。
- Apple WWDC 2013中介紹的Custom Transition