先從一個簡單的轉場動畫開始,了解自定義轉場動畫的過程。
Color Diffusion Transition
第一個畫面背景是綠色的,中間有個紅色的箱子,當點下紅色的箱子以後,箱子會擴散出紅色至整個畫面。
第二個畫面背景是紅色的,中間有個綠色的箱子,當點下綠色的箱子以後,紅色的背景會縮到中間的箱子當中。
HomeViewController
點下箱子以後會去 present 第二個畫面,這裡比一般的 present 多了兩個設定,讓 ViewController 知道我們要自定義轉場動畫。
1 2 3 4 |
let VC = DetailViewController() VC.modalPresentationStyle = .custom VC.transitioningDelegate = diffustionTransition present(VC, animated: true, completion: nil) |
另外我們會告訴 diffusionTransition 我們要在哪一個 View 上做動畫(從那裡擴散和從那裡收縮)
這個畫面的例子就是中間的按鈕了。
1 |
diffustionTransition = SKDiffussionTransition(animatedView: button) |
SKDiffusionTransition
繼承於 NSObject, UIViewControllerAnimatedTransitioning
在這個方法中返回動畫的 duration
1 |
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval |
另外會實作 UIViewControllerTransitioningDelegate 中實現兩個方法。
通過改變自己定義的 isReverse 屬性,讓 SKDiffusionTransition 知道目前是要做 present 還是 dismiss 的動畫效果。
1 2 3 4 5 6 7 8 9 10 11 |
extension SKDiffussionTransition: UIViewControllerTransitioningDelegate { open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { isReverse = false return self } open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { isReverse = true return self } } |
轉場動畫的實現
具體的轉場動畫內容都會在這個方法中實現
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // get frame and backgroundColor var startFrame = CGRect.zero if animatedView != nil { startFrame = animatedView!.frame startBackgroundColor = animatedView!.backgroundColor } // init animated view for transition let animatedViewForTransition = UIView(frame: startFrame) animatedViewForTransition.clipsToBounds = true animatedViewForTransition.layer.cornerRadius = animatedViewForTransition.frame.height / 2.0 animatedViewForTransition.backgroundColor = self.startBackgroundColor // add animated view on transitionContext's containerView transitionContext.containerView.addSubview(animatedViewForTransition) // set presentedController let presentedController: UIViewController if !isReverse { presentedController = transitionContext.viewController(forKey: .to)! presentedController.view.layer.opacity = 0 } else { presentedController = transitionContext.viewController(forKey: .from)! } presentedController.view.frame = transitionContext.containerView.bounds transitionContext.containerView.addSubview(presentedController.view) let size = max(transitionContext.containerView.frame.height, transitionContext.containerView.frame.width) * 1.2 let scaleFactor = size / animatedViewForTransition.frame.width let finalTransform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor) if !self.isReverse { UIView.transition(with: animatedViewForTransition, duration: self.transitionDuration(using: transitionContext) * 0.7, options: [], animations: { animatedViewForTransition.transform = finalTransform animatedViewForTransition.center = transitionContext.containerView.center animatedViewForTransition.backgroundColor = presentedController.view.backgroundColor },completion: nil) UIView.animate(withDuration: self.transitionDuration(using: transitionContext) * 0.4, delay: self.transitionDuration(using: transitionContext) * 0.6, animations: { presentedController.view.layer.opacity = 1 },completion: { (_) in animatedViewForTransition.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } else { animatedViewForTransition.transform = finalTransform animatedViewForTransition.center = transitionContext.containerView.center animatedViewForTransition.backgroundColor = presentedController.view.backgroundColor UIView.animate(withDuration: self.transitionDuration(using: transitionContext) * 0.7, animations: { presentedController.view.layer.opacity = 0 }) DispatchQueue.main.asyncAfter(deadline: .now() + self.transitionDuration(using: transitionContext) * 0.3) { UIView.transition(with: animatedViewForTransition, duration: self.transitionDuration(using: transitionContext) * 0.6, options: [], animations: { animatedViewForTransition.transform = CGAffineTransform.identity animatedViewForTransition.backgroundColor = self.startBackgroundColor animatedViewForTransition.frame = startFrame }, completion: { (_) in animatedViewForTransition.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } } } } |
Reference
- 可以在 Github 上看到本文相關的 Source Code