教你实现类似于格瓦拉启动页中的放大转场动画(OC&Swift)

教你实现类似于格瓦拉启动页中的放大转场动画(OC&Swift)

一、前言

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

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

laye.mask

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

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

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

添加导航控制器

然后效果如下

去掉导航栏

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

新建一个viewController

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

按钮的约束位置以及大小

添加按钮以及背景色以后效果

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

实现第一个 VC 按钮点击方法

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

设置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效果,现在准备工作已经做好了,想要实现放大效果的动画,还要继续往下进行。

三、开始实现放大效果

通过上面的步骤,我们已经做好了准备工作,我们还要知道的一点是,要想自定义导航的push或pop效果,需要实现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 animateTransition(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的几个状态

  • Begin :手势被识别时时,初始化UIPercentDrivenInteractiveTransition实例对象和设置属性,比如如果是第一个VC就实现push,反之则是pop
  • Changed:开始手势到结束手势的一个过程,上面代码中是根据偏移量改变self.interactionController的位置
  • Ended:手势结束以后的操作,设置动画结束或者取消动画,最后将self.interactionController置为nil
  • default:其他的状态运行你的工程,拖拽屏幕时效果如下

pan_swift.gif

五、最后

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

时间: 2024-10-24 23:26:59

教你实现类似于格瓦拉启动页中的放大转场动画(OC&Swift)的相关文章

app启动页的设计技巧(一)

虽然,每一个APP的启动页界面设计是需要按使用场景设计的.但是每一个APP启动页的存在的意义与APP产品定位.APP运营策略有关的. 第一部分:对于设计一个好的APP启动页作品,必须满足这三个特征 认真观察,很容易归纳出欢迎页有以下的三个特征: 1.简单:在启动页中,文案是极为简短精炼的,而启动页一般由一张图片和一句文案组成: 2.直接:启动页中的文字表述简单直接,基本没有过多的修饰性词语: 3.图为主,文为辅:启动页中,图片约占三分之二的区域,文字约占三分之一的区域 第二部分:简单易学的APP

给用户惊喜还是惊吓?做好APP的第一帧:启动页

做得再夺目的启动页只起辅助性的功能,这就是启动页的特殊性.启动页之所以只能起到辅助性的作用原因在于启动页并不是用户下载并使用应用的主要目的,用户的主要目的是使用应用的某一项功能.同时这个原因也导致了启动页获得的用户停留度和注意度是极低的. 笔者这周的工作之一是负责公司应用启动页的文案策划.其实,看似‘多余’的启动页是存在一定的逻辑的.浏览了一定数量的启动页后,笔者将在本文中就移动应用启动页的使用场景与特殊性.启动页的作用.启动页的特征.启动页的设计技巧这四个方面来帮助大家更好地理解应用的启动页.

Android中启动页ViewPager和ViewFlipper带指示器

版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 首先我们来分析一下,想要实现启动页的功能,大家第一个想到的就是使用ViewPager,使用ViewPager确实是一种比较好的方式,而且思路也是比较清晰的.今天我们就一起来学习一下,使用ViewPager和ViewFlipper实现启动页带小点功能. 先展示一下图片,看看是不是你想要的效果.  1.ViewPager和ViewFlipper的区别 ViewFlipper继承ViewAnimator,切换view的时候是有动画

在chromium中加入默认的启动页

启动页的配置选项如图所示: 启动页的默认配置由src\chrome\browser\prefs\session_startup_pref.cc生成 在RegisterProfilePrefs函数中,可以看到配置的选项. registry->RegisterIntegerPref( prefs::kRestoreOnStartup, TypeToPrefValue(GetDefaultStartupType()), user_prefs::PrefRegistrySyncable::SYNCABL

IOS-上架APP之启动页设置(新手必看!)

今天自己做的小作品准备提交,就差一个启动页,各种百度,各种搜,结果还好最后终于出来了,和大家分享一下,这个过程中遇到的各种小问题.(注XCode版本为7.2) 1.启动页一般都是图片,因为苹果有4,4S,5,5S,6,6S可能以后还会有其他的型号,所以要考虑到屏幕的适配,还有系统的适配(有些用户的系统版本不支持一些技术,比如LaunchScreen.storyboard),也就是图片的大小,那么多大的图片呢,图片的大小是可以在XCode里找到的(当时各种百度,各种搜大,答案也是众说纷纭,所以看过

app为什么要有启动页(Splash screen)

1.包名(Package name)在Android系统中是判断一个App的唯一标识. 2.启动页Activity简单的来说Activity指的就是App中我们看到的页面,一个页面就是一个Activity,通常第一页启动起来的页面我们就称之为 "启动页Activity". 3.为什么要有启动页(闪屏) ①闪屏首先是交互的响应,就像一个按钮凸起,点击它如果没有凹陷或者变色等变化,用户会觉得不可点击或者是木有反应,卡机,垃圾.闪屏就是一个最直接迅速的交互响应. ②其次,通过闪屏可以有效捕捉

HBuilder开发App教程03-定制图标,启动页以及打包

helloworld 上次说到了helloworld.你应该已经能够新建项目.真机调试了, 这次来说说图标的定制,启动页的定制以及打包. 图标定制 假设不定制图标的话,默认会是博文顶部图表的样子, 你肯定不想这个样子,来吧.定制一个自己的图标吧. 打开项目下的manifest.json,选择底部的图标配置(老版本号需升级到最新版). 然后选择一个制作好的256*256大小的png,系统会自己主动生成各种大小的图标. 启动页定制 步骤和上面一样,至不要要选择启动图片配置一项. 然后依照各种图片大小

iOS 快速集成启动页广告

前言 由于项目中要用到启动页广告,所以做了简单的研究,同时借鉴网易新闻和蘑菇街的交互写了一个简单的demo,现在写出来供大家参考,可能由于个人局限会有一些bug和不完善的地方,也希望大家能够友善提醒和指正. Github地址:https://github.com/Running2snail/LLFullScreenAd 效果图如下: 代码分析:上面主要展示了广告图提过按钮显示的两种方式,一种是常见的计数倒计时+跳过的样式(大部分的广告启动页都是这种方式),一种是通过环形倒计时+跳过的样式(仿网易

MVC设置启动页

设置启动页需要在路由中添加一段代码: public static void RegisterRoutes(RouteCollection routes)        {            routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(                name: "Default",                url: "{controller}/