美文网首页Realank的iOS专题iOS开发iOS学习
一步步教你实现类似于格瓦拉启动页中的放大转场动画(Objecti

一步步教你实现类似于格瓦拉启动页中的放大转场动画(Objecti

作者: HenryCheng | 来源:发表于2016-03-16 23:13 被阅读9210次

    一、前言


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

    tap_oc

    iOS中,在同一个导航控制器你可以自定义转场动画实现两个viewController之间的过渡。实际上在iOS7之后,通过实现UIViewControllerAnimatedTransitioning或者UIViewControllerContextTransitioning协议,就可以简单的自定义转场动画,比如一个NavigationControllerpushpop
    还有一点你需要知道的是,我如果有一个矩形,有一个圆,想要在这个矩形上剪出和圆大小相同的面积,那么就要用到CALayermask属性,下面用图表达可能会直观些:

    laye.mask

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

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


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

    添加导航控制器

    然后效果如下

    去掉导航栏

    把右侧的Shows Navigation Bar去掉,因为这个demo里面并不需要导航栏,同时保证Is Initial View Controller是被勾上的(不知道的童鞋可以去掉看一下效果),这里默认的都是勾选上的。
    然后在新建一个viewController,并设置其继承于ViewController,如下

    新建一个viewController

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

    按钮的约束位置以及大小 添加按钮以及背景色以后效果
    然后右键一直按住第一个按钮拖拽至第二个VC(也就是黄色背景的)点击show
    实现第一个 VC 按钮点击方法

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

    设置`identifier`
    将两个按钮连接成ViewController的同一个属性,名为popBtn,然后将第二个VC的按钮实现一个点击方法(因为我们要pop回来)名为popClick,如下
    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,最后的效果图如下

    最后效果图

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

    最后的运行效果图

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

    三、开始实现放大效果


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

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

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

    //
    //  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里,

    添加`Object`
    并在选中Object,在右边将其类改成刚刚创建的HWNavigationDelegate HWNavigationDelegate.png
    最后在左侧,点击UINavigationController,并将其delegate设置为刚才的Object 设置导航的`delegate`

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

    //
    //  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释放掉的操作。
    里面的

            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这个上面开始的时候就说过了,第一段代码其实就是一个计算的过程,求出最后大圆效果的半径,原理如图(粗糙的画了一下,画得不好见谅*_*

    动画效果关键的实现原理图

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

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

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

    tap_swift

    四、添加手势引导动画


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

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

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

    设置导航属性

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

    //
    //  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:其他的状态
      运行你的工程,拖拽屏幕时效果如下
    pan_swift.gif

    五、最后


    由于最近工作比较忙,好久没有写博客了,趁着这回功夫将这个小动画分享一下,希望大家喜欢,时间不早了,该回去休息了(在公司加班完成的,喜欢的就star一下吧),最后,这里只是swift版本的代码,同时如果你需要全部代码的话,你可以在下面下载

    相关文章

      网友评论

      • 虫子_Gray:希望能出个纯代码的,我用代码跟着做 有问题,第二个页面整个页面上移了
      • 新地球说着一口陌生腔调:你好 能说话你xib写的页面,怎么执行到动画代码的?
      • 照亮黑夜的曙光:大神有没有OC的 不会Sf:cold_sweat:
        照亮黑夜的曙光:@HenryCheng 谢谢 看见了 学习了:smile:
        HenryCheng:@_SX代码两个版本的都有
      • 一瓶红花油:求swift3的代码啊,新手看不懂。。。
        HenryCheng:@一瓶红花油 GitHub 上源码应该最开就更新了,你去看看
      • 暖暖的太阳::sweat:
        HenryCheng:@暖暖的太阳 这是啥表情
      • a1ca31b58342:麻烦请问作者,签订代理的时候maskLayerAnimation.delegate = self,告诉我不允许赋值啊😂
      • Codepgq:写的不错,研究一下!!!!
      • 44ec90b7efa7:楼上说的不懂代码怎么使用 应该就是不懂您这里HWNavigationDelegate在代码中怎么引入吧,您没有解释那个object是什么,表示纯代码引入有障碍.
      • 将书:MARK
      • 57c4e0896953:博主我想问个问题,思考了很久还是不知道怎么解决。
        首先你这个很不错我想引用这个方法到我的sample中,你这个例子是通过VC1上的一个按钮跳转到VC2,但是我的Sample情况有些不同这就导致我的问题了,希望指点下,我的情景是我的VC1上有很多TableviewCell,因为TableviewCell是动态的,我想通过点击TableviewCell上的某个圆形的imageview 实现你这个效果跳转到另个VC2,不知道怎么实现。 因为tableviewcell上的imageview已经在tableviewcell类里声明过一个outlet变量了,所以无法在这里做出你例子中类似的popBtn那个类似的操作,会提示不能再repeating content上设置outlet,可是我的场景就是想点击某个tableviewcell上的圆形imageview触发你这个转场效果,不知道有没有说清楚,希望指点下。
      • Flonger:收藏
      • df57d5e6a14d:不错 标记
      • 悦思:大神,照着你的思路实现了一遍,push 和 pop现在都是向外扩张的圆,我想做一个pop向内收缩的转场,(比如)两个动画的圆心固定在左边一个位置,我使用了两个动画器,一个做push 另一个做pop 紧紧是互换了动画 的fromValue和ToValue,但是pop的动画是瞬间就回到上一个控制器的View然后再做收缩的动画, 最后一闪显示上一页的view,想跟你讨论一下如何实现这个pop 的平滑收缩动画,就是和格瓦拉一样子的。。。求指教
      • 0ef1bd497653:很实用,学习了
      • YwWyW:学习了
      • 虚心若愚:辛苦啦!学习了。
      • ReidWang:在 animateTransition 方法里给 toVC.view.layer 设置 mask, 然而为什么 却在 animationDidStop 里 给 fromVC.view.layer 的 mask 置为 nil 呢?有这么一个疑惑。总之谢谢楼主! :+1:
        ReidWang:@HenryCheng 我打了断点测试了一下发现,当第一次 push 的时候,此时, fromVC.layer 的mask 为 nil,toVC.layer 的 mask 和 animateTransition 方法里 添加的 mask 是同一个,如果仅是这样的话,我觉得在结束的时候将 toVC.layer 的 mask 置为 nil 好一点点。谢谢楼主了,看了一天总算记住了,😄,谢谢楼主啦! :)
        HenryCheng:@di3 这是两个VC之间的跳转,这里就是VC1和VC2,第一次的时候 VC1->VC2 ,这里fromVC就是VC1,toVC就是VC2,VC2的layer。mask是有的吧,第二次的时候 VC2 ->VC1,这里的fromVC就是VC2了,是吧,所以需要将其置为nil
      • 11be181de0ca:Mark 不知道纯代码能否实现这个效果呢?
        HenryCheng:@s1mpl3 你可以加上导航然后再隐藏
        11be181de0ca:@HenryCheng 纯代码的话 没有Nav怎么push呢?
        HenryCheng:@s1mpl3 肯定能啊,代码原理和sb应该是一样的啊
      • __sev7:weak var transitionContext: UIViewControllerContextTransitioning?

        这一句中的transitionContext 为什么是weak 可以简单解释一下么
        HenryCheng:@__sev7 UIViewControllerContextTransitioning这是一个协议
      • 温暖的弦Y:纯代码,怎么调用这个动画呢?个人比较喜欢纯代码
        虚心若愚:我是小白,设置导航控制器的代理 怎么那样设置啊?
        温暖的弦Y:@HenryCheng 对啊,但是不知道怎么设置导航控制器的delegate是你写的那个代理
        HenryCheng:@Patty_温暖的弦 纯代码无非是界面都用代码写喽
      • 别问为什么:不喜欢用storyboard,个人感觉还是用代码来的思路清晰,storyboard只适合用来做适配...还有就是有一个bug,手势从右往左滑动屏幕会闪一下...
        HenryCheng:@别问为什么 嗯,已发现,修复后会更新
      • 柚子CHA:非常实用很适合用到项目中
        HenryCheng:@636bb53ff9e6 :smile:
      • 8b858db87184:有两处文字错误
        1.增加导航栏的时候is Initial View Controller钩打上,initial错啦
        2.设置时间和动画的方法

        func animateTransition(transitionContext: UIViewControllerContextTransitioning)
        这个方法写了两遍 :grin:
        HenryCheng:@快乐的木木金冈 谢谢指正,已修改
      • ee690f2e90a4:手势有个瑕疵,如果我往左滑,放手后,屏幕会闪一下
        HenryCheng:@low神 嗯,谢谢提醒,后面会修复
      • ee690f2e90a4:很详细,只要按着做,就能成功跑起来了。
      • newbiecoder:哎呦 我擦甩我好几条街了
        HenryCheng:@newbiecoder :sweat:
      • wittyfan:喜欢
      • Ryan文濤:点个赞~~
      • zmj27404:学习了!!

      本文标题:一步步教你实现类似于格瓦拉启动页中的放大转场动画(Objecti

      本文链接:https://www.haomeiwen.com/subject/nsaqlttx.html