博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一步步教你实现类似于格瓦拉启动页中的放大转场动画(Objective C & Swift)
阅读量:6940 次
发布时间:2019-06-27

本文共 10129 字,大约阅读时间需要 33 分钟。

一、前言


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

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

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

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


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

然后效果如下

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

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

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

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

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

import UIKitclass 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 UIKitclass 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,在右边将其类改成刚刚创建的
HWNavigationDelegate

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

现在上面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 UIKitclass 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()    }复制代码

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

四、添加手势引导动画


添加手势实现动画效果,我们在刚才的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 UIKitclass 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:其他的状态 运行你的工程,拖拽屏幕时效果如下

五、最后


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

  • 1、(swift版本)
  • 2、 (OC版本)

转载地址:http://cginl.baihongyu.com/

你可能感兴趣的文章
linux下/boot目录丢失的恢复
查看>>
进来看看吧 多学点知识不亏.......
查看>>
新手学习arm的建议
查看>>
记一次被中间人攻击的经历
查看>>
原来你是这样的Websocket--抓包分析
查看>>
mysql Navicat通过代理链接数据库
查看>>
把网站发布到远程服务器上
查看>>
解析特殊locale的日期格式
查看>>
发布Acey.ExcelX3.2
查看>>
微信接口出现“调用支付jsapi缺少参数appid”
查看>>
利用ResultFilter实现asp.net mvc3 页面静态化
查看>>
PHP 图片加水印的方法
查看>>
javascript报错集锦
查看>>
koa2实现拦截器进行登录前session校验
查看>>
[java]窗口中的菜单项
查看>>
[ACM] hdu 2191 珍惜现在,感恩生活 (多重背包)
查看>>
python-并发编程之多进程
查看>>
JavaScript严格模式详解
查看>>
小X的佛光 NOIP模拟赛 倍增LCA 树结构
查看>>
The Himalayas (zoj 3809)
查看>>