语言:Swift
ios控制转场动画的两块大砖:
animated动画:UIViewControllerAnimatedTransitioning
interactive交互:UIViewControllerInteractiveTransitioning
animated是在自定义转场动画时必须实现的一个代理,用以告诉系统具体动画细节,而interactive是依附animated存在的交互控制代理。意思是当你自定义了一个animated动画后,通过实现interactive代理来告诉系统怎么用交互控制这个动画。
但实际项目中控制交互一般用的是UIPercentDrivenInteractiveTransition,它是系统提供的interactive的一个实现类,并提供了百分比控制的方式来很方便地控制动画交互。
简单点说,自定义动画器Animator实现UIViewControllerAnimatedTransitioning协议,用交互器UIPercentDrivenInteractiveTransition或其子类来控制动画交互
一、先说说在哪里实现:
- 如果是present模态控制器时,需要实现UIViewControllerTransitioningDelegate协议,并赋值给controller的transitioningDelegate属性。这个协议一般用的就四个方法:
// 动画器
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 交互器
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
- 如果是自定义导航器内push或pop动画时,需要实现的是UINavigationControllerDelegate协议里面的两个方法,并赋值给navigationController的delegate:
// 动画器
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 交互器
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
一般系统会先通过动画器的代理询问有无自定义的动画,无就使用系统自带的转场方式,有就再通过交互器的代理询问是否需要交互性
二、再看看简单实现方式
动画器:遵循animated协议必须实现的两个方法
// 转场时间
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// 动画细节
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
一个简单的present动画器就可以写成
class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var duration: TimeInterval = 0.6
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 可以使用as?获取具体控制器的引用,针对特定的视图做效果
guard let to = transitionContext.viewController(forKey: .to) else { return }
guard let from = transitionContext.viewController(forKey: .from) else { return }
// 视图容器,动画效果需要在里面完成
let container = transitionContext.containerView
container.addSubview(from.view)
to.view.alpha = 0
container.addSubview(to.view)
// 简单效果
UIView.animate(withDuration: self.duration, animations: {
to.view.alpha = 1
}) { (finished) in
if finished {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
}
交互器:拿UIPercentDrivenInteractiveTransition的对象直接使用,使用时候一般随着手势
// 当前动画执行的百分比
open var percentComplete: CGFloat
// 控制动画执行到哪里,值0到1
open func update(_ percentComplete: CGFloat)
// 取消动画,恢复原状
open func cancel()
// 完成剩余的动画
open func finish()
比如定义两个控制器FirstViewController和SecondViewController,在FirstViewController的view加一个拖拽手势,通过向下拖拽来present SecondViewController(我们使用上面写的一个简单动画器):
class FirstViewController: UIViewController {
private var interactive: UIPercentDrivenInteractiveTransition?
override func viewDidLoad() {
super.viewDidLoad()
interactive = UIPercentDrivenInteractiveTransition.init()
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(self.pan(_:)))
self.view.addGestureRecognizer(panGesture)
}
@objc private func pan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
self.presentItem() // 在手势开始时present控制器
} else if gesture.state == .changed {
let offset = gesture.translation(in: self.view)
if offset.y > 0 && offset.y < 400 {
interactive?.update(offset.y/400) // 用向下偏移量来控制动画执行百分比
}
} else if gesture.state == .cancelled {
interactive?.cancel() // 取消动画
} else if gesture.state == .ended {
// 手势结束时判断结束或取消动画
if interactive!.percentComplete > 0.4 {
interactive?.finish()
} else {
interactive?.cancel()
}
}
}
private func presentItem() {
let second = SecondViewController.init()
second.modalPresentationStyle = .fullScreen
second.transitioningDelegate = self // 自定义动画的代理
self.present(second, animated: true, completion: nil)
}
}
extension FirstViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PresentAnimator.init() // 动画器
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactive // 交互器
}
}
当需要自定义导航器内的push或pop动画时,可以把动画器跟交互器放到自定义的导航器下:
class MyNavigationController: UINavigationController {
private var interactive: UIPercentDrivenInteractiveTransition?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
interactive = UIPercentDrivenInteractiveTransition.init()
}
}
extension MyNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if fromVC.isKind(of: FirstViewController.self), toVC.isKind(of: SecondViewController.self), operation == .push {
return PushAnimator.init() // 给一个push动画器
} else if fromVC.isKind(of: SecondViewController.self), toVC.isKind(of: FirstViewController.self), operation == .pop {
// 特别的,当执行pop动画时,second控制器是fromVC,first控制器是toVC
return nil
}
return nil
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if animationController.isKind(of: PushAnimator.self) {
return interactive
}
return nil
}
}
这么看起来还是挺简单的,其实不然。当一个导航器下众多不同的动画与交互器堆到了一起,一些动画还需要特定视图的引用,并且还需要动态选择某个动画器的时候,管理起来是个不小的灾难。下节看看实际项目中该怎么进行管理