登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

一步步教你实现类似于格瓦拉启动页中的放大转场动画 [复制链接]

2018-4-23 11:17
BeatBeat 阅读:960 评论:0 赞:1
Tag:  

一、前言

用过格瓦拉电影,或者其他app可能都知道,一种点击按钮用放大效果实现转场的动画现在很流行,效果大致如下

2.gif

在iOS中,在同一个导航控制器你可以自定义转场动画实现两个viewController之间的过渡。实际上在iOS7之后,通过实现UIViewControllerAnimatedTransitioning或者UIViewControllerContextTransitioning协议,就可以简单的自定义转场动画,比如一个NavigationController的push和pop。

还有一点你需要知道的是,我如果有一个矩形,有一个圆,想要在这个矩形上剪出和圆大小相同的面积,那么就要用到CALayer的mask属性,下面用图表达可能会直观些:

1.jpg

现在可能你对mask属性有一点了解了,下面代码的实现中你将会看到具体的实现过程。先做这么多了解,下面开始一步步实现效果。

二、开始实现简单的push效果

新建工程,这里用的是Swift,选中storyboard,然后加上一个导航,如下

1.png

然后效果如下

1.png

把右侧的Shows Navigation Bar去掉,因为这个demo里面并不需要导航栏,同时保证Is Initial View Controller是被勾上的(不知道的童鞋可以去掉看一下效果),这里默认的都是勾选上的。

然后在新建一个viewController,并设置其继承于ViewController,如下

1.png

然后在两个VC上分别在同样的位置添加两个完全相同的按钮,位置约束在右上角距离右边和上边分别为20,20的距离,为了区分,将这两个VC设置不同的背景色,如下

1.png

1.png

然后右键一直按住第一个按钮拖拽至第二个VC(也就是黄色背景的)点击show

1.png

这时候两个VC之间就会出现一条线,然后点击线中间,设置identifier为PushSegue,这里设置一个标识符,为后面的跳转做准备,效果如下:

1.png

将两个按钮连接成ViewController的同一个属性,名为popBtn,然后将第二个VC的按钮实现一个点击方法(因为我们要pop回来)名为popClick,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var popBtn: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
   
    @IBAction func popClick(sender: AnyObject) {
         
        self.navigationController?.popViewControllerAnimated(true)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

最后,分别在两个VC的中间添加一个imageView,最后的效果图如下

1.png

如果到这里你还没错的话,那么运行一下你的工程,运行的效果将会是这样

2.gif

没错,也就是一个简单的push效果,现在准备工作已经做好了,想要实现放大效果的动画,还要继续往下进行。

三、开始实现放大效果

通过上面的步骤,我们已经做好了准备工作,我们还要知道的一点是,要想自定义导航的push或pop效果,需要实现UINavigationControllerDelegate协议里面的

1
2
3
4
   func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return nil
    }

这个协议方法,我们先新建一个继承于NSObject的名为HWNavigationDelegate的一个类,然后引入UINavigationControllerDelegate,实现上面的协议方法,使返回值暂时为nil(从上面代码中可以看出返回值是一个可选值,所以这里可以先用nil,待会再具体实现)。然后你的HWNavigationDelegate里面的代码大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
//  HWNavigationDelegate.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//
import UIKit
class HWNavigationDelegate: NSObject, UINavigationControllerDelegate {
     
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
         
       return nil;
    }
}

现在继续打开storyboard,然后在右下角搜索Object,并将其拖拽至左边Navigation Controller Source里,

1.png

并在选中Object,在右边将其类改成刚刚创建的HWNavigationDelegate

1.png

最后在左侧,点击UINavigationController,并将其delegate设置为刚才的Object

1.png

现在上面HWNavigationDelegate里面导航的协议方法的返回值还是nil,我们需要创建一个实现动画效果的类,并使其返回,这里我们新建一个同样继承于NSObject的名为HWTransitionAnimator的类,并使其实现UIViewControllerAnimatedTransitioning协议,和其中的协议方法,为了便于阅读,这里贴出所有的代码,

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
//
//  HWTransitionAnimator.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//
import UIKit
class HWTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    weak var transitionContext: UIViewControllerContextTransitioning?
     
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
         
        return 0.5
    }
     
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
         
        self.transitionContext = transitionContext
         
        let containerView = transitionContext.containerView()
        let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController
        let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! ViewController
        let button = fromVC.popBtn
         
        containerView?.addSubview(toVC.view)
         
        let circleMaskPathInitial = UIBezierPath(ovalInRect: button.frame)
        let extremePoint = CGPoint(x: button.center.x - 0, y: button.center.y - CGRectGetHeight(toVC.view.bounds))
        let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
        let circleMaskPathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius))
         
        let maskLayer = CAShapeLayer()
        maskLayer.path = circleMaskPathFinal.CGPath
        toVC.view.layer.mask = maskLayer
         
        let maskLayerAnimation = CABasicAnimation(keyPath: "path")
        maskLayerAnimation.fromValue = circleMaskPathInitial.CGPath
        maskLayerAnimation.toValue = circleMaskPathFinal.CGPath
        maskLayerAnimation.duration = self.transitionDuration(transitionContext)
        maskLayerAnimation.delegate = self
        maskLayer.addAnimation(maskLayerAnimation, forKey: "path")
         
    }
     
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
         
        self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled())
        self.transitionContext?.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view.layer.mask = nil
    }
}

关于上面的所有代码,其中func transitionDuration(transitionContext: UIViewControllerContextTransitioning?),func animateTransition(transitionContext: UIViewControllerContextTransitioning)分别是设置时间和动画的方法,都是UIViewControllerAnimatedTransitioning的协议方法,func animationDidStop是实现动画结束后的操作,这里动画结束后需要做取消动画和将fromViewController释放掉的操作。

里面的

1
2
3
4
5
6
7
8
        let circleMaskPathInitial = UIBezierPath(ovalInRect: button.frame)
        let extremePoint = CGPoint(x: button.center.x - 0, y: button.center.y - CGRectGetHeight(toVC.view.bounds))
        let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
        let circleMaskPathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius))
         
        let maskLayer = CAShapeLayer()
        maskLayer.path = circleMaskPathFinal.CGPath
        toVC.view.layer.mask = maskLayer

这段代码,下面第二段代码的maskLayer这个上面开始的时候就说过了,第一段代码其实就是一个计算的过程,求出最后大圆效果的半径,原理如图(粗糙的画了一下,画得不好见谅^_^)

1.png

最后将刚才HWNavigationDelegate里的协议方法返回值修改成HWTransitionAnimator的对象就可以了

1
2
3
4
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
         
        return HWTransitionAnimator()
    }

如果上面步骤,你操作没错的话,运行工程效果如下

2.gif

四、添加手势引导动画

添加手势实现动画效果,我们在刚才的HWNavigationDelegate类里实现UINavigationControllerDelegate的另外一个斜一方法

1
2
3
4
    func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return self.interactionController
    }

这里的self.interactionController就是我们的导航控制器,如下图

1.png

然后重写awakeFromNib()方法,关于整个HWNavigationDelegate最后的代码实现,如下

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
//
//  HWNavigationDelegate.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//
import UIKit
class HWNavigationDelegate: NSObject, UINavigationControllerDelegate {
     
    @IBOutlet weak var navigationController: UINavigationController!
    var interactionController: UIPercentDrivenInteractiveTransition?
     
    func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return self.interactionController
    }
     
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
         
        return HWTransitionAnimator()
//        return nil;
    }
     
    override func awakeFromNib() {
        super.awakeFromNib()
        let panGesture = UIPanGestureRecognizer(target: self, action: Selector("panned:"))
        self.navigationController.view.addGestureRecognizer(panGesture)
    }
     
    func panned(gestureRecognizer: UIPanGestureRecognizer) {
        switch gestureRecognizer.state {
        case .Began:
             
            self.interactionController = UIPercentDrivenInteractiveTransition()
            if self.navigationController?.viewControllers.count > 1 {
                self.navigationController?.popViewControllerAnimated(true)
            else {
                self.navigationController?.topViewController!.performSegueWithIdentifier("PushSegue", sender: nil)
            }
        case .Changed:
             
            let translation = gestureRecognizer.translationInView(self.navigationController!.view)
            let completionProgress = translation.x / CGRectGetWidth(self.navigationController!.view.bounds)
            self.interactionController?.updateInteractiveTransition(completionProgress)
        case .Ended:
             
            if (gestureRecognizer.velocityInView(self.navigationController!.view).x > 0) {
                self.interactionController?.finishInteractiveTransition()
            else {
                self.interactionController?.cancelInteractiveTransition()
            }
            self.interactionController = nil
             
        default:
            self.interactionController?.cancelInteractiveTransition()
            self.interactionController = nil
        }
    }
}

这里需要注意的是gestureRecognizer的几个状态

  • 1、Begin :手势被识别时时,初始化UIPercentDrivenInteractiveTransition实例对象和设置属性,比如如果是第一个VC就实现push,反之则是pop

  • 2、Changed:开始手势到结束手势的一个过程,上面代码中是根据偏移量改变self.interactionController的位置

  • 3、Ended:手势结束以后的操作,设置动画结束或者取消动画,最后将self.interactionController置为nil

  • 4、default:其他的状态运行你的工程,拖拽屏幕时效果如下

2.gif

分享到:
我来说两句
facelist
您需要登录后才可以评论 登录 | 立即注册
所有评论(0)
关闭

每日头条

通过邮件订阅最新 Code4App 信息
上一条 /4 下一条
联系我们
关闭
合作电话:
13802416937
Email:
435399051@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| Github|申请友链|手机版|Code4App ( 粤ICP备15117877号-1 )

返回顶部