有关转场动画的文章,多如牛毛,也有很多大神的文章,或是开源demo,我从中精简了主要流程,以及自己的开发经验,写了这篇文章,希望有所帮助。
早期的时候有一个Demo,在github上:
本想做一些转场动画的library,但是由于种种原因搁浅了。
本文也将结合这个Demo来做一些补充。
https://github.com/quinn0809/PresentTranslationDemo
一、CATranlation
的转场动画
引用:iOS 动画 —— CATransition : https://www.jianshu.com/p/239cf81eb1eb
这个适合uiview的转场,或者简单的viewcontroller的转场,估计很难满足你的需求。
二、UIViewControllerTransitioningDelegate
与 UINavigationControllerDelegate
这样讲:一个复杂的、自定义的、完整的转场动画,包括入场手势交互
+入场动画
+出场手势交互
+出场动画
然而,大部分转场动画为:入场动画
+出场动画
或入场手势交互
+入场动画
+出场动画
或入场动画
或出场手势交互
+出场动画
或入场动画
+出场手势交互
+出场动画
UIViewControllerAnimatedTransitioning 定义了转场动画的细节:包括时间和具体的转场动画
UIPercentDrivenInteractiveTransition 定义了转场动画的手势进度,是否取消,是否完成等
UIViewControllerTransitioningDelegate 只是定义了我该如何选择转场动画,如何选择手势交互
也就是说:UIViewControllerTransitioningDelegate 是管理UIPercentDrivenInteractiveTransition 和 UIViewControllerAnimatedTransitioning 的
联系如下:
UIViewControllerTransitioningDelegate
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return X<UIViewControllerAnimatedTransitioning>
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Y<UIViewControllerAnimatedTransitioning>
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return Z<UIViewControllerInteractiveTransitioning>
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
//ToDo:present interaction
return W<UIViewControllerInteractiveTransitioning>
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
//ToDo:.....
return nil
}
到目前为止:你已经了解了30%了。
注意:UIViewControllerTransitioningDelegate
--》present、dismiss
这个适用于模态转场。
那么导航转场动画呢?
UINavigationControllerDelegate
----》 push、pop
//动画操作
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
if operation == .push {
return X<UIViewControllerAnimatedTransitioning>
}else{
return Y<UIViewControllerAnimatedTransitioning>
}
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return Z<UIViewControllerInteractiveTransitioning>
}
}
再来看:UIViewControllerAnimatedTransitioning
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
switch type {
case .pop:
doPopAnimation(transitionContext: transitionContext)
case .push:
doPushAnimation(transitionContext: transitionContext)
}
}
简单的转场动画
func doPushAnimation(transitionContext:UIViewControllerContextTransitioning) {
let screenBounds = UIScreen.main.bounds
//from vc view
guard let from = transitionContext.viewController(forKey: .from)else{
return
}
from.view.frame = CGRect.init(x: 0, y: 0, width: screenBounds.width, height: screenBounds.height)
//to vc view
guard let to = transitionContext.viewController(forKey: .to) else {
return
}
to.view.frame = CGRect.init(x: 0, y: 0, width: screenBounds.width, height: screenBounds.height)
to.view.alpha = 0
/**转场动画UIView容器
*/
let containView = transitionContext.containerView
containView.addSubview(from.view)
containView.addSubview(to.view)
let duration: TimeInterval = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
to.view.alpha = 1
}) { (_) in
let wasCancelled: Bool = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!wasCancelled)
}
}
func doPopAnimation(transitionContext:UIViewControllerContextTransitioning) {
let screenBounds = UIScreen.main.bounds
//from vc view
guard let from = transitionContext.viewController(forKey: .from)else{
return
}
from.view.frame = CGRect.init(x: 0, y: 0, width: screenBounds.width, height: screenBounds.height)
//to vc view
guard let to = transitionContext.viewController(forKey: .to) else {
return
}
to.view.frame = CGRect.init(x: 0, y: 0, width: screenBounds.width, height: screenBounds.height)
to.view.alpha = 0
/**转场动画UIView容器
*/
let containView = transitionContext.containerView
containView.addSubview(from.view)
containView.addSubview(to.view)
let duration: TimeInterval = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
to.view.alpha = 1
}) { (_) in
let wasCancelled: Bool = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!wasCancelled)
}
}
转场动画,基本完成,下面我们加入手势:
不太好描述,我们来看这个文件:
import UIKit
class QInteractiveTranslationManager: UIPercentDrivenInteractiveTransition {
weak var vc:UIViewController?
var gestureDirection:QGestureDirection = .down
private(set) var interation:Bool = false
private(set) var transitionType:QTranslateType = .dismiss
init(gestureDirection:QGestureDirection) {
super.init()
self.gestureDirection = gestureDirection
}
@objc func handleGesture(gesture:UIPanGestureRecognizer){
var present:CGFloat = 0
switch gestureDirection {
case .left:
let transitionX = -gesture.translation(in: gesture.view).x
present = transitionX/(gesture.view?.frame.size.width ?? 0)
case .right:
let transitionX = gesture.translation(in: gesture.view).x
present = transitionX/(gesture.view?.frame.size.width ?? 0)
case .up:
let transitionY = -gesture.translation(in: gesture.view).y
present = transitionY/(gesture.view?.frame.size.height ?? 0)
case .down:
let transitionY = gesture.translation(in: gesture.view).y
present = transitionY/(gesture.view?.frame.size.height ?? 0)
}
switch gesture.state {
case .began:
interation = true
startGesture()
case .changed:
//更新百分比
update(present)
case .ended:
interation = false
if present > 0.3{
self.finish()
}else{
self.cancel()
}
default:
break
}
}
func addPanGestureForViewController(_ viewController:UIViewController ){
let pan = UIPanGestureRecognizer.init(target: self, action: #selector(handleGesture(gesture:)))
self.vc = viewController
self.vc?.view.addGestureRecognizer(pan)
}
func startGesture(){
switch transitionType {
case .dismiss:
vc?.dismiss(animated: true, completion: nil)
case .pop:
vc?.navigationController?.popViewController(animated: true)
default:
break
}
}
}
为了较少耦合,我决定用一个
class QTransitionController: NSObject,UIViewControllerTransitioningDelegate,UINavigationControllerDelegate
来控制转场动画,但是由于大部分转动画具有高度的不可重用性,所以,想要实现一个结构较好的方式,比较困难。
QTransitionController
可以用于实现总控制,
但是具体的手势交互需要根据需求,QInteractiveTranslationManager
实现了这一点
QPresentTransitionAnimatorLibrary
有present、push的入场动画,
QDismissTransitionAnimatorLibrary
有dismiss、pop的出场动画.
你可以下载来看,demo中并不齐全,但大体思路是这样的,希望有人能补充。