3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果

这是自定义转场系列的第四篇。由于具有一定的连续性,我会忽略一些基础,所以如果你是第一次看这个系列,可以先过目之前的几篇 ——— UIViewControllerTransitioning的用法 、实现Keynote中的神奇移动效果、实现通过圆圈放大缩小的转场动画。

老规矩,先端上GIF。

How to work

首先在StoryBoard上拖两个UIViewController。并且在第一个VC上放一个button,使用Action Segue连接到第二个VC。

然后回到代码界面。和以往一样,我们需要创建两个文件:一个用于从第一个VC过渡到第二个VC的动画(如push),另一个这是第二个过渡到第一个VC的动画(如pop)。这里不得不说iOS7中引入的这种解耦合的方式,它的意义在于无论在哪儿需要用到转场动画的地方,直接把这两个文件扔过去就行了。

我们创建两个文件:KYPushTransition 和 KYPopTransition 。从名字可以看出,后一个是前一个的反转动画。其实,我们完全可以把两个文件写在一起:KYTransition 。因为两个文件的代码结构几乎别无二致,不同的地方也只要用布尔值区分一下就行了。但这里为了让介绍思路清晰,我们把两个动画分开来实现。

首先是KYPushTransition。

先继承 UIViewControllerAnimatedTransitioning 协议。实现下面两个方法:

- (NSTimeInterval)transitionDuration:(id )transitionContext{

//动画的时间

return 0.6f;

}

- (void)animateTransition:(id )transitionContext{

//动画的逻辑

...

}

下面具体介绍动画的逻辑。

- (void)animateTransition:(id )transitionContext{

//1

FirstViewController *fromVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

SecondViewController *toVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

UIView *fromView = fromVC.view;

UIView *toView = toVC.view;

UIView *containerView = [transitionContext containerView];

[containerView addSubview:toView];

[containerView sendSubviewToBack:toView];

//2

CATransform3D transform = CATransform3DIdentity;

transform.m34 = -0.002;

containerView.layer.sublayerTransform = transform;

//3

CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];

fromView.frame = initialFrame;

toView.frame = initialFrame;

//4

[self updateAnchorPointAndOffset:CGPointMake(0.0, 0.5) view:fromView];

//5

CAGradientLayer *gradient = [CAGradientLayer layer];

gradient.frame = fromView.bounds;

gradient.colors = @[(id)[UIColor colorWithWhite:0.0 alpha:0.5].CGColor,

(id)[UIColor colorWithWhite:0.0 alpha:0.0].CGColor];

gradient.startPoint = CGPointMake(0.0, 0.5);

gradient.endPoint = CGPointMake(0.8, 0.5);

UIView *shadow = [[UIView alloc]initWithFrame:fromView.bounds];

shadow.backgroundColor = [UIColor clearColor];

[shadow.layer insertSublayer:gradient atIndex:1];

shadow.alpha = 0.0;

[fromView addSubview:shadow];

//6

[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{

//旋转fromView 90度

fromView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1.0, 0);

shadow.alpha = 1.0;

} completion:^(BOOL finished) {

//7

fromView.layer.anchorPoint = CGPointMake(0.5, 0.5);

fromView.layer.position = CGPointMake(CGRectGetMidX([UIScreen mainScreen].bounds), CGRectGetMidY([UIScreen mainScreen].bounds));

fromView.layer.transform = CATransform3DIdentity;

[shadow removeFromSuperview];

[transitionContext completeTransition:YES];

}];

}

解释一下。

1)通过上下文transitionContext获得前后两个UIView,这也是发生动画的具体对象。同时还需要获得containerView,这也是动画发生的地方。我们需要把后一个视图添加上去。为了保证后一个视图加上去之后不遮住前一个视图的动画,我们还要把后一个视图放到最后:[containerView sendSubviewToBack:toView];

2)为了保证视图产生3D的效果,我们需要设置layer的仿射变换。关于仿射变化和m34的概念,推荐一篇博客:iOS的三维透视投影。

3)为fromView、toView设置初始frame。

4)重置锚点。锚点就是视图旋转时候的中心,就是那个不动的点。关于锚点以及position的关系,你可以参考这一篇解释:这将是你最后一次纠结position与anchorPoint!。所以我们在设置了锚点的之后,还需要把layer的position也设置到相应位置:

-(void)updateAnchorPointAndOffset:(CGPoint)anchorPoint view:(UIView *)view{

view.layer.anchorPoint = anchorPoint;

view.layer.position = CGPointMake(0, CGRectGetMidY([UIScreen mainScreen].bounds));

}

方便记忆,你可以理解锚点会吸附到position上。所以光改变锚点不改变position,那么结果就是锚点确实改了,但是position还是在默认的(0.5,0.5),也就是视图中心。就像这样:

5)给fromView增加左深右浅的阴影。并且一开始的透明度为0,随着翻转的角度变大过渡到1。

6)开始动画。这这里,我们让fromView翻转90度 fromView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1.0, 0);。这里要注意向外90度是-M_PI_2,y为1.0表示绕着y轴旋转。

7)动画结束,我们需要还原锚点的位置、恢复position的位置、恢复layer的transform为CATransform3DIdentity,并且把阴影层移除。由于一开始我没有没有恢复锚点和position的位置,而且一直没找到原因。知道我查看了视图的层级结构才恍然大悟:

可见,使用控制台的”Debug View Hierarchy“

是多么有用!

当然,还有最重要最关键的一步:[transitionContext completeTransition:YES];。告诉上下文,动画已经完成。如果你这里不这么做,你将无法从后一个视图返回前一个视图。

好了,至此已经完成所有push动画逻辑。实现pop的逻辑基本无二。

KYPopTransition

1、首先就是删除[containerView sendSubviewToBack:toView];,这时我们可不想让动画躲在后面。

2、第二个不同点,需要加上

//让toView的截图旋转90度

toView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0.0, 1.0, 0.0);

因为这时动画的起始应该先保持在90度的位置,然后慢慢过渡到0度。

3、阴影层的需要加在toView上,并且起始透明度应该为1,终止时为0。

How to Use

1、如果你是一个VC present 到另一个VC,那么FirstViewController和SecondViewController都需要继承协议是UIViewControllerTransitioningDelegate。

然后在FirstViewController中设置代理。

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

SecondViewController *secVC = (SecondViewController *)segue.destinationViewController;

secVC.transitioningDelegate = self;

[super prepareForSegue:segue sender:sender];

}

并实现协议的两个方法:分别对应present和dismiss。

- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{

KYPushTransition *flip = [KYPushTransition new];

return flip;

}

- (id )animationControllerForDismissedController:(UIViewController *)dismissed{

KYPopTransition *flip = [KYPopTransition new];

return flip;

}

2、如果你用UINavigationController去控制两个VC,此时如果你什么都不做,segue会自动变成标准的导航栏推进的push、pop。要使用我们自定义的动画,需要让FirstViewController继承协议UINavigationControllerDelegate,然后设置代理为自己self.navigationController.delegate = self;。

实现UINavigationControllerDelegate中的相关协议方法,只需要一个:

- (id )navigationController:(UINavigationController *)navigationController

animationControllerForOperation:(UINavigationControllerOperation)operation

fromViewController:(UIViewController *)fromVC

toViewController:(UIViewController *)toVC{

if (operation == UINavigationControllerOperationPush) {

KYPushTransition *flip = [KYPushTransition new];

return flip;

}else if (operation == UINavigationControllerOperationPop){

KYPopTransition *flip = [KYPopTransition new];

return flip;

}else{

return nil;

}

}

搞定,现在就运行了。

Where to go —— 如何为转场增加手势交互

如果是一个VC present 到另一个VC,那么就要实现UIViewControllerTransitioningDelegate中的两个方法,和UIViewControllerAnimatedTransitioning一样,分别对应 present 和 dismiss的动画。

- (id )interactionControllerForPresentation:(id )animator;

- (id )interactionControllerForDismissal:(id )animator;

这时,你可能已经被各种协议代理名字搞晕了,是的,我一开始也晕了,但只要多练几次还是能就熟悉的。

既然我们之前是创建了两个文件继承UIViewControllerAnimatedTransitioning来实现过渡动画,那么是不是也应该继承UIViewControllerInteractiveTransitioning来实现百分比交互动画呢?因为在图中,两者的关系是并列的。是的,你可以这么做。但是苹果提供了一个更好的类 ———— UIPercentDrivenInteractiveTransition。

顾名思义,我们就可以猜到这个类就是专门用来实现手势百分比交互的。怎么使用呢?

为了遵循解耦合,我们新建一个UIPercentDrivenInteractiveTransition的子类 —— KYPopInteractiveTransition。

创建一个方法-(void)addPopGesture:(UIViewController *)viewController;,用来给目标视图控制器添加一个边缘滑动手势:

-(void)addPopGesture:(UIViewController *)viewController{

presentedVC = viewController;

UIScreenEdgePanGestureRecognizer *edgeGes = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(edgeGesPan:)];

edgeGes.edges = UIRectEdgeLeft;

[viewController.view addGestureRecognizer:edgeGes];

}

实现相应的手势方法:

-(void)edgeGesPan:(UIScreenEdgePanGestureRecognizer *)edgeGes{

//1

CGFloat translation =[edgeGes translationInView:presentedVC.view].x;

CGFloat percent = translation / (presentedVC.view.bounds.size.width);

percent = MIN(1.0, MAX(0.0, percent));

NSLog(@"%f",percent);

switch (edgeGes.state) {

case UIGestureRecognizerStateBegan:{

//2

self.interacting = YES;

[presentedVC dismissViewControllerAnimated:YES completion:nil];

//如果是navigationController控制,这里应该是[presentedVC.navigationController popViewControllerAnimated:YES];

break;

}

case UIGestureRecognizerStateChanged:{

//3

[self updateInteractiveTransition:percent];

break;

}

case UIGestureRecognizerStateEnded:{

//4

self.interacting = NO;

if (percent > 0.5) {

[self finishInteractiveTransition];

}else{

[self cancelInteractiveTransition];

}

break;

}

default:

break;

}

}

1)计算手指在X轴方向上的偏移距离,与屏幕的宽度的之比保存为一个百分比。也就是说,当手指划过屏幕的距离超过屏幕宽度的1/2,那么剩下的动画就自动完成;否则,取消动画。这里用了MIN和MAX把百分比始终控制在了0~1之间。

2)滑动开始,指定要执行的操作。这里因为没有使用UINavigation控制两个VC,所以是dismissViewControllerAnimated:。如果是用UINavigation去控制的,那么这里相应的应该是navigationController popViewControllerAnimated:。 self.interacting的作用稍后揭晓。

3)在UIGestureRecognizerStateChanged 调用 [self updateInteractiveTransition:percent]。这里我们把刚才的百分比传了过去,系统就可以通过这个0~1的竖数值实时改变动画的进度。

4)当UIGestureRecognizerStateEnded的时候,我们需要判断此时手指是否划过屏幕大于一半的距离。如果大于一半,告诉系统完成:[self finishInteractiveTransition]; 反之,告诉系统取消操作:[self cancelInteractiveTransition],这时动画也将返回初始位置。

特别注意,当我们使用了手势百分比交互,在相应的动画逻辑KYPopTransition中,把原来的[transitionContext completeTransition:YES] 改成 [transitionContext completeTransition:![transitionContext transitionWasCancelled]]。如果一直是YES的话,当我们手指划过小于屏幕一半,即使系统知道是取消动画,但在上下文中依然是写死的YES。

使用手势百分比交互

在FirstViewController中的-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender里面,创建一个KYPopInteractiveTransition的实例并把SecondVC传过去:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

...

popInteractive = [KYPopInteractiveTransition new];

[popInteractive addPopGesture:secVC];

...

}

然后实现UIViewControllerTransitioningDelegate里的这个关于手势百分比交互的方法:

- (id )interactionControllerForDismissal:(id )animator{

return popInteractive.interacting ? popInteractive : nil;

}

好了,这里你发现我们使用了popInteractive.interacting来判断,还记得之前买的关子吗?self.interacting的作用就在这里。因为- (id )interactionControllerForDismissal:(id )animator 这个方法在 点击dismiss和滑动dismiss时候都会调用。然而如果只是return popInteractive;的话,当我们点击dismiss的时候,程序将不会做出反应。所以,我们需要区分,到底是点击dismiss还是滑动dismiss。因此,我们需要一个布尔值来判断,就是这样。

这个系列应该差不多到这里就结束了。本篇的源码你可以在这里获得。

有任何疑问,欢迎在下方评论区域留言:D

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/264162.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[转]算术编码+统计模型=数据压缩 - 第二部分:统计模型

转自:http://deercrane.spaces.live.com/blog/cns!8BEF692B75EB8095!189.entry 算术编码 统计模型 数据压缩 - 第二部分:统计模型 (撼庭秋译自http://compression.graphicon.ru/download/articles/ppm/nelson/arithmetic2.htm) …

链式栈的实现(头文件及源程序)

链式栈的实现(头文件及源程序) Linkedstack.h #ifndef __LINKEDSTACK_H__ #define __LINKEDSTACK_H__//元素类型定义 typedef int ElemType_stack;//结点结构体类型定义 typedef struct Node_stack {ElemType_stack data;struct Node_stack *next;stru…

phper必知必会之类库自动加载的七种方式(三)

## php自动加载 下面显示例子的文件目录结构图 一、没有使用命名空间的几种实现 test/oneClass.php class oneClass{public function show(){echo "这里是oneClass.php的show方法<br/>";}} test/twoClass.php <?phpclass twoClass{public function show(){…

怎样的项目才算是一个成功的项目 于病视神,未有形而除之

http://www.nowamagic.net/librarys/veda/detail/2466参与各种项目已经好几年了&#xff0c;大大小小的项目也做过了很多。经历了很多&#xff0c;也明白了很多。现在反思下&#xff0c;什么样的项目才是成功的项目&#xff1f; 是把项目按照必要的约束&#xff08;时间&#x…

sqlserver55555_sqlserver把小数点后面多余的0去掉

Sql中想把小数点后多余的0去掉,怎么办?select 5000/10000.0 --想变成0.5select 5500/10000.0 --想变成0.55select 5550/10000.0 --想变成0.555select 5555/10000.0 --想变成0.5555其结果分别为&#xff1a;0.5000000 0.5500000 0.5550000 0.5555000一、如果想去掉数字5后…

一个产品留言统计查寻的分析比较

有产品表Product(ProductId,Name,Username,AddTime...) 留言表 Agency(AgencyId, ProductId, TargetUsername,IsRead...)其中Agency.TargetUsername与Product.Username指这个产品的发布用户(以及这条留言的目标用户--不是指发留言的人), 现在要打印某一指定用户的如下列表…

链式队列的实现(头文件及源程序)

链式队列的实现&#xff08;头文件及源程序&#xff09; Linkedqueue.h #ifndef __LINKEDQUEUE_H__ #define __LINKEDQUEUE_H__//元素类型定义 typedef int ElemType_Queue;//结点结构体类型定义 typedef struct Node_Queue {ElemType_Queue data;struct Node_Queue *next;}N…

单点登录的实现

单点登录的实现 token验证&#xff1a;https://blog.csdn.net/sleepwalker_1992/article/details/82974703转载于:https://www.cnblogs.com/yishenweilv/p/10764653.html

c++ 箭头符号怎么打_焊接图纸符号标注图解示例,焊接符号标注实例及方法

基本坡口符号坡口符号(注&#xff1a;图中“破”应为“坡”)焊接图纸符号标注图解示例焊接符号标注实例及方法在焊接结构图样上&#xff0c;焊接方法可按国家标准GB5185-85的规定用阿拉伯效字表示&#xff0c;标注在指引线的尾部。常用焊接方法代号见表3-9所示。如果是组合焊接…

Tekson的数据结构程序9——搜索

9. 搜索 搜索方法有&#xff1a;顺序搜索法&#xff08;即链表搜索法&#xff09;、二分搜索法、二叉树搜索法、哈希表搜索法、TRIE树搜索法。 其中&#xff0c;链表搜索法的搜索速度为&#xff1b;二分搜索法与二叉树搜索法的搜索速度为&#xff1b;哈希表的搜索速度为&#x…

演示: GTS流量×××和CAR流量监管的效果及相关实践计划

演示&#xff1a; GTS流量和CAR流量监管的效果及相关实践计划演示目标&#xff1a;1 理解clock rate(时钟频率)和bandwidth&#xff08;带宽&#xff09;与接入速率的关系2 在模拟运营商的接入路由器ISP上配置CAR监管用户流量到认购速率64K3 取证模拟的企业网络以128K的接入速率…

给所有的交叉编译工具创建软链接

给所有的交叉编译工具创建软链接 #!/bin/bash #给所有的交叉编译工具创建软链接echo "正在给所有的交叉编译工具创建软链接..." filenamsls arm-linux-gnueabi-* #获取当前文件夹下的所有以arm-linux-gnueabi-文件名开头的文件名 cont0 #记录软链接完成个数…

写在前面

硕士毕业在即&#xff0c;我的纯CV之路即将开始&#xff0c;老师提到了潜在的研究方向&#xff0c;一个全新的领域&#xff0c;可以说是新的起点。未来四年&#xff0c;我要想不碌碌而为&#xff0c;不像硕士期间一样“泛泛而学”&#xff0c;我必须从理论到实践都做好非常扎实…

httpclient封装获取响应实体_Httpclient 接口自动化

好久木写啦&#xff01;&#xff01;&#xff01;好久木写啦&#xff01;&#xff01;&#xff01;心血来潮分享点小白的东西&#xff01;&#xff01;&#xff01;废话少说直接干货&#xff01;&#xff01;&#xff01;本文核心是将如何从数据驱动开始&#xff0c;以报告结尾…

服务器控件开发之基本概念

利用ASP.NET 2.0技术&#xff0c;创建Web自定义服务器控件并不是一件轻松的事情。因为&#xff0c;这需要开发人员了解并能够灵活应用多种Web开发技术&#xff0c;例如&#xff0c;CSS样式表、客户端脚本语言、.NET开发语言、服务器控件开发技术&#xff0c;甚至是当前最火的AJ…

如何在Django模板中注入全局变量

我们在做一些网站项目的时候&#xff0c;可能会遇到需要把某个全局变量注入到所有页面的情况&#xff0c;比如我们做一个在线商城&#xff0c;那么可能需要将用户的资料&#xff1a;比如用户的账号、用户的姓名等注入到每个页面里面。但是如果用常规的方法&#xff0c;也就是在…

JavaScript 定时器

setInterval 每隔time时间后执行内部代码&#xff0c;后边改的time时间并不起作用 var time 2000; var obj setInterval(() > {}, time);//obj 返回的是一个数字&#xff0c;作为唯一标识time200; //不起作用clearInterval(obj) //清除计时器setInterval("console.…

Ubuntu18.04更换为国内源

Ubuntu18.04更换为国内源 1、打开Linux终端&#xff0c;执行下列命令&#xff0c;将源文件备份&#xff0c;以防万一。 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak2、选择下列任意一个源&#xff0c;将其复制 ​ 2.1 阿里源 deb http://mirrors.aliyun.com/…

如何关闭大疆gps_如何使用djisdkforwindows从mavic 2获取gps数据?

我使用windows sdk。我正在尝试使用以下方法获取gps数据:var flightControllerHandler _djiManagerInstance.ComponentManager.GetFlightControllerHandler(ProductIndex, ComponentIndex);flightControllerHandler.AircraftLocationChanged - QuadcopterOnAircraftLocationCh…

跨线程访问控件

【转帖】 我们在做winform应用的时候&#xff0c;大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题&#xff0c;下面我将详细的介绍。 首先来看传统方法&#xff1a; public partial class Form1 : Form { public Fo…