教你实现类似于格瓦拉启动页中的放大转场动画(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 UIKitclass ViewController: UIViewController {@IBOutlet weakvarpopBtn: 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? {returnnil}
这个协议方法,我们先新建一个继承于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? {returnnil;}
}
现在继续打开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 UIKitclass HWTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {weakvartransitionContext: UIViewControllerContextTransitioning?func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return0.5}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {self.transitionContext = transitionContextlet containerView = transitionContext.containerView()let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewControllerlet toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! ViewControllerlet button = fromVC.popBtncontainerView?.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.CGPathtoVC.view.layer.mask = maskLayerlet maskLayerAnimation = CABasicAnimation(keyPath:"path")maskLayerAnimation.fromValue = circleMaskPathInitial.CGPathmaskLayerAnimation.toValue = circleMaskPathFinal.CGPathmaskLayerAnimation.duration = self.transitionDuration(transitionContext)maskLayerAnimation.delegate = selfmaskLayer.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.CGPathtoVC.view.layer.mask = maskLayer
这段代码,下面第二段代码的maskLayer这个上面开始的时候就说过了,第一段代码其实就是一个计算的过程,求出最后大圆效果的半径,原理如图(粗糙的画了一下,画得不好见谅^_^)
动画效果关键的实现原理图
最后将刚才HWNavigationDelegate里的协议方法返回值修改成HWTransitionAnimator的对象就可以了
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {returnHWTransitionAnimator()}
如果上面步骤,你操作没错的话,运行工程效果如下
tap_swift
四、添加手势引导动画
添加手势实现动画效果,我们在刚才的HWNavigationDelegate类里实现UINavigationControllerDelegate的另外一个斜一方法
func navigationController(navigationController: UINavigationController,interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {returnself.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 weakvarnavigationController: UINavigationController!varinteractionController: UIPercentDrivenInteractiveTransition?func navigationController(navigationController: UINavigationController,interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {returnself.interactionController}func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {returnHWTransitionAnimator()// return nil;}override func awakeFromNib() {super.awakeFromNib()let panGesture = UIPanGestureRecognizer(target: self, action: Selector("panned:"))self.navigationController.view.addGestureRecognizer(panGesture)}func panned(gestureRecognizer: UIPanGestureRecognizer) {switchgestureRecognizer.state {case.Began:self.interactionController = UIPercentDrivenInteractiveTransition()ifself.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 = nildefault: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版本的代码,同时如果你需要全部代码的话,你可以在下面下载
HWAnimationTransition_Swift(swift版本)
HWAnimationTransition_OC (OC版本)
教你实现类似于格瓦拉启动页中的放大转场动画(OC&Swift)的更多相关文章
- App启动页设计实例与技巧
App启动页,也称闪屏页,最初是为缓解用户等待Web/iOS/Android App数据加载的焦虑情绪而出现,后被设计师巧妙用于品牌文化展示,服务特色介绍以及功能界面熟悉等平台进行设计,被赋予了更加丰 ...
- 微信小程序之启动页的重要性
启动页在APP中是个很常见的需求,为什么对于小程序来说也非常重要呢?首先我描述一下我在开发过程中遇到的一些问题以及解决的步骤,到最后为什么要加启动页,看完你就明白了. 小程序的首页需要展示用户关注的小 ...
- ASP.NET MVC 设置Area中 Controller 的方法 默认启动页
MVC中通常分区域编程,互不干扰,如果需要设置某个区域下面的某个控制器下面的某个方法为默认启动页的话,直接修改项目的路由如下: public static void RegisterRoutes(Ro ...
- Android中启动页ViewPager和ViewFlipper带指示器
版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 首先我们来分析一下,想要实现启动页的功能,大家第一个想到的就是使用ViewPager,使用ViewPager确实是一种比 ...
- Expo大作战(十五)--expo中splash启动页的详细机制
简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...
- C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理
C#编译器优化那点事 使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...
- .NET MVC项目设置包含Areas中的页面为默认启动页
利用vs创建一个MVC项目后,一般的默认启动页是根目录下-->Controllers-->HomeController-->Index这个方法对应的页面. 我先说下创建Areas的流 ...
- React Native商城项目实战06 - 设置安卓中的启动页
1.Main 目录下新建LaunchImage.js: /** * 启动页 */ import React, { Component } from 'react'; import { AppRegis ...
- ionic 图标以及启动页图片不能正确加载
前段时间莫名其妙的发现发布的app不能正常的现实图标和启动页了,加载出来的图标以及图片显示的都是cordova的默认图片以及启动页图片 在网上找了很多教程各种查找都不能解决方法 表现原因为: 项目根目 ...
随机推荐
- linux安装dubbo
(1)下载dubbo-admin-2.4.1.war包 https://github.com/alibaba/dubbo.git 1.打开cmd 2.cd到dubbo的根目录下,我的dubbo根目录 ...
- [SourceTree]--记录Win10 安装SourceTree免注册登陆
记录SourceTree一次安装不成功的过程及解决办法 SourceTree简介 按照官网介绍:SourceTree是一款用于Windows和Mac的免费Git客户端.简化了用户与Git存储(仓)库的 ...
- Scala实战高手****第17课:Scala并发编程实战及Spark源码阅读
package com.wanji.scala.test import javax.swing.text.AbstractDocument.Content import scala.actors.Ac ...
- React Native之iOS App打包
iOS打包步骤(一.二.三可不按照顺序) 步骤一: 选择iOS Device(以下两者选其中一个即可) 选择 Generic iOS Device (个人建议使用这个) 选择Generic iOS D ...
- 【摘】请问make -j8 和make -j4 是什么意思?什么作用?
看到别人写的Shell脚本中有 make -j8 等字眼,Google了一下,网友说是: make linux kernel/rootfs时候多线程执行.
- pkav漏洞之精华部分
影响巨大的逻辑型漏洞 TOM邮箱任意密码秒改 网易手机邮箱任意密码重置强行绑定 交通银行免费买车漏洞 中国建设银行刷人民币漏洞 腾讯任意QQ号码注册 微信任意用户密码修改漏洞 新浪微搏修改任意用户密码 ...
- CentOS7.x 通过mail命令发,使用465端口(smtps协议)发送邮件
#创建证书mkdir -p /root/.certs/echo -n | openssl s_client -connect smtp.qq.com:465 | sed -ne '/-BEGIN CE ...
- nmcli日常用法
一.nmcli日常用法nmcli dev status //查看系统现有网络设备的连接状态nmcli conn show //查看已有连接nmcli conn delete UUID1 UUID2 U ...
- 基于3D Vision眼镜的OSG立体显示 【转】
http://blog.csdn.net/qq_20038925/article/details/50510565 OSG 立体显示 3D Vision眼镜:所实现的是被动立体. 1.本人最近在做os ...
- webpack配置:图片处理、css分离和路径问题
一.CSS中的图片处理: 1.首先在网上随便找一张图片,在src下新建images文件夹,将图片放在文件夹内 2.在index.html中写入代码:<div id="pic" ...