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

本文是投稿文章,作者:HenryCheng (简书上的链接: http://www.jianshu.com/p/8c29fce5a994)


一、前言

用过格瓦拉电影,或者其他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,如下


importUIKit
classViewController:UIViewController{
@IBOutletweak var popBtn:UIButton!
overridefuncviewDidLoad(){
super .viewDidLoad()
//Doanyadditionalsetupafterloadingtheview,typicallyfromanib.
}
@IBActionfuncpopClick(sender:AnyObject){
self.navigationController?.popViewControllerAnimated( true )
}
overridefuncdidReceiveMemoryWarning(){
super .didReceiveMemoryWarning()
//Disposeofanyresourcesthatcanberecreated.
}
}

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

最后效果图

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

最后的运行效果图

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

三、开始实现放大效果

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

funcnavigationController(navigationController:UINavigationController,
interactionControllerForAnimationControlleranimationController:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{
return nil
}

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

//
//HWNavigationDelegate.swift
//HWAnimationTransition_Swift
//
//CreatedbyHenryChengon16/3/16.
//Copyright?2016年www.igancao.com.Allrightsreserved.
//
importUIKit
classHWNavigationDelegate:NSObject,UINavigationControllerDelegate{
funcnavigationController(navigationController:UINavigationController,animationControllerForOperationoperation:UINavigationControllerOperation,fromViewControllerfromVC:UIViewController,toViewControllertoVC: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
//
//CreatedbyHenryChengon16/3/16.
//Copyright?2016年www.igancao.com.Allrightsreserved.
//
importUIKit
classHWTransitionAnimator:NSObject,UIViewControllerAnimatedTransitioning{
weak var transitionContext:UIViewControllerContextTransitioning?
functransitionDuration(transitionContext:UIViewControllerContextTransitioning?)->NSTimeInterval{
return 0.5
}
funcanimateTransition(transitionContext:UIViewControllerContextTransitioning){
self.transitionContext=transitionContext
letcontainerView=transitionContext.containerView()
letfromVC=transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)as!ViewController
lettoVC=transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)as!ViewController
letbutton=fromVC.popBtn
containerView?.addSubview(toVC.view)
letcircleMaskPathInitial=UIBezierPath(ovalInRect:button.frame)
letextremePoint=CGPoint(x:button.center.x-0,y:button.center.y-CGRectGetHeight(toVC.view.bounds))
letradius=sqrt((extremePoint.x*extremePoint.x)+(extremePoint.y*extremePoint.y))
letcircleMaskPathFinal=UIBezierPath(ovalInRect:CGRectInset(button.frame,-radius,-radius))
letmaskLayer=CAShapeLayer()
maskLayer.path=circleMaskPathFinal.CGPath
toVC.view.layer.mask=maskLayer
letmaskLayerAnimation=CABasicAnimation(keyPath: "path" )
maskLayerAnimation.fromValue=circleMaskPathInitial.CGPath
maskLayerAnimation.toValue=circleMaskPathFinal.CGPath
maskLayerAnimation.duration=self.transitionDuration(transitionContext)
maskLayerAnimation.delegate=self
maskLayer.addAnimation(maskLayerAnimation,forKey: "path" )
}
overridefuncanimationDidStop(anim:CAAnimation,finishedflag: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释放掉的操作。里面的

letcircleMaskPathInitial=UIBezierPath(ovalInRect:button.frame)
letextremePoint=CGPoint(x:button.center.x-0,y:button.center.y-CGRectGetHeight(toVC.view.bounds))
letradius=sqrt((extremePoint.x*extremePoint.x)+(extremePoint.y*extremePoint.y))
letcircleMaskPathFinal=UIBezierPath(ovalInRect:CGRectInset(button.frame,-radius))
letmaskLayer=CAShapeLayer()
maskLayer.path=circleMaskPathFinal.CGPath
toVC.view.layer.mask=maskLayer

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

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

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

return HWTransitionAnimator()

}

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

tap_swift

四、添加手势引导动画

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

interactionControllerForAnimationControlleranimationController:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{

return self.interactionController
}


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

设置导航属性

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

//
//HWNavigationDelegate.swift
//HWAnimationTransition_Swift
//
//CreatedbyHenryChengon16/3/16.
//Copyright?2016年www.igancao.com.Allrightsreserved.
//
importUIKit
classHWNavigationDelegate:NSObject,UINavigationControllerDelegate{
@IBOutletweak var navigationController:UINavigationController!
var interactionController:UIPercentDrivenInteractiveTransition?
funcnavigationController(navigationController:UINavigationController,
interactionControllerForAnimationControlleranimationController:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{
return self.interactionController
}
funcnavigationController(navigationController:UINavigationController,toViewControllertoVC:UIViewController)->UIViewControllerAnimatedTransitioning?{
return HWTransitionAnimator()
//returnnil;
}
overridefuncawakeFromNib(){
super .awakeFromNib()
letpanGesture=UIPanGestureRecognizer(target:self,action:Selector( "panned:" ))
self.navigationController.view.addGestureRecognizer(panGesture)
}
funcpanned(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:
lettranslation=gestureRecognizer.translationInView(self.navigationController!.view)
letcompletionProgress=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版本的代码,同时如果你需要全部代码的话,你可以在下面下载

  • HWAnimationTransition_Swift(swift版本)

  • HWAnimationTransition_OC(OC版本)

  • 版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐


    软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
    现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
    前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
    【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
    前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
    因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
    【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
    用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
    采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
    随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
    基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
    数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
    1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
    Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
    音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
    腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
    音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
    C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
    本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
    在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。