0%

IBAnimatable转场动画-Explode学习

阅读源码后可以发现,和一般自定义转场动画一致,新建继承 NSObject 子类,遵守 UIViewControllerAnimatedTransitioning 协议。

实现两个代理方法:

  • 返回动画持续时间代理:
    1
    2
    3
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 0.75
    }
  • 自定义动画代理:
    1
    2
    3
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {    
    // 自定义动画函数
    }

参数 transitionContext 可以可以通过 func viewForKey(key: String) -> UIView? / public func viewControllerForKey(key: String) -> UIViewController? 取出转场动画的对应 fromView/toView / formViewController/toViewController 对应的 key 值:

1
2
3
4
5
6
7
viewForKey:
UITransitionContextFromViewKey
UITransitionContextToViewKey

viewControllerForKey:
UITransitionContextFromViewControllerKey
UITransitionContextToViewControllerKey

Explode 动画中主要在于屏幕快照的获取以及快照的区域分剪,核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取 fromView 的快照
let fromViewSnapshot = fromView.snapshotViewAfterScreenUpdates(false)

// 将快照剪切成小块加到 containerView 上
for x in 0.0.stride(to: Double(size.width), by: Double(size.width / xFactor)) {
for y in 0.0.stride(to: Double(size.height), by: Double(size.height / yFactor)) {
let snapshotRegion = CGRect(x: CGFloat(x), y: CGFloat(y), width: size.width / xFactor, height: size.height / yFactor)

// 按所给区域获得快照的小块
let snapshot = fromViewSnapshot.resizableSnapshotViewFromRect(snapshotRegion, afterScreenUpdates: false, withCapInsets: UIEdgeInsetsZero)
// 主要是设置位置
snapshot.frame = snapshotRegion
// 将拼成的 fromView 快照加到 containerView的最顶层
containerView.addSubview(snapshot)
snapshots.append(snapshot)
}
}

// 将 fromView 隐藏
containerView.sendSubviewToBack(fromView)

剩下的就是对 每一个小块的动画处理,并在动画结束后调用:

1
ransitionContext.completeTransition(!transitionContext.transitionWasCancelled())

这都很简单,难的是如何结合手势使用,这是最值得学习的地方,理解不深,可以 clone 源码 学习。

实现过程主要是对 UIPercentDrivenInteractiveTransition 的学习使用,和 IBAnimatable 的实现不同,我们采用 NavigationController 管理界面,在 FirstViewControllerfunc viewWillAppear(animated: Bool) {} 内设置代理: navigationController?.delegate = self
(如果在方法: func viewDidLoad() {} 设置代理会导致转场取消后无法再次进行自定义动画转场)
实现代理方法:

1
2
3
4
5
6
7
8
9
10
extension FirstViewController: UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == UINavigationControllerOperation.Push {
// ExplodeAnimator 即为自定义的转场动画
return ExplodeAnimator()
} else {
return nil
}
}
}

之后就是对 SecondViewController 内进行自定义手势 popViewController :
首先对 view 添加返回手势:

1
2
3
4
5
view.addGestureRecognizer({
let pan = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(SecondViewController.pan(_:)))
pan.edges = UIRectEdge.Left
return pan
}())

手势回调方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func pan(edgePan: UIScreenEdgePanGestureRecognizer) {

let progress = edgePan.translationInView(self.view).x / self.view.bounds.width

if edgePan.state == UIGestureRecognizerState.Began {
self.percentDrivenTransition = UIPercentDrivenInteractiveTransition()
self.navigationController?.popViewControllerAnimated(true)
} else if edgePan.state == UIGestureRecognizerState.Changed {
self.percentDrivenTransition?.updateInteractiveTransition(progress)
} else if edgePan.state == UIGestureRecognizerState.Cancelled || edgePan.state == UIGestureRecognizerState.Ended {
if progress > 0.5 {
self.percentDrivenTransition?.finishInteractiveTransition()
} else {
self.percentDrivenTransition?.cancelInteractiveTransition()
}
self.percentDrivenTransition = nil
}
}

同样,在 SecondViewControllerfunc viewWillAppear(animated: Bool) {} 方法内设置代理: navigationController!.delegate = self,区别只是在于多实现一个代理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension SecondViewController: UINavigationControllerDelegate {

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == UINavigationControllerOperation.Pop {
return ExplodeAnimator()
} else {
return nil
}
}

func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if animationController is ExplodeAnimator {
return self.percentDrivenTransition
} else {
return nil
}
}
}

大功告成,
效果展示:
Demo
代码地址: CodeDemo
IBAnimatable 源码的实现基于高度的封装,这也是望尘莫及的地方。

欢迎关注我的其它发布渠道