【POP动画引擎教程 01】实现图片折叠效果
2016-05-13 11:26
519 查看
直到我看到了POP,我的热情像刚拔开瓶盖的香槟一样喷射出来,心中那份对iOS动画的热爱的火星也被彻底点燃。因为我的终极目标是,成为一名交互动画Master。
好,吹了那么多牛让我们讲点靠谱的。今天我要手把手教你实现的一个POP动画是这样的:
![](http://kittenyang.com/content/images/2015/Jan/2015-01-09-00_05_10.gif)
首先我们来分析一下。
要让一张照片从中间折过来,如果直接是把一张照片折中而且还要让上下部分显示出不同的阴影,这会非常麻烦。所以,我们使用的技巧是,把一张图切成均等的两部分。然后把切割后的两张图分别作为两个独立的view的image。当它们上下紧贴的时候看上去就像一张完整的图片,但其实是两个视图上下合并的。
有了这个思路,我们接着往下走。
怎么让上半部分view绕着水平中轴线旋转?首先中轴线的位置可以通过重写锚点的位置设置; 绕X轴旋转可以使用
然后我们会想到,上半视图绕X轴旋转的角度肯定和手指滑动的距离有关。
好了,整体思路就是这样,十分符合情理。唯一需要拐个弯的小技巧就是我们把一张照片割成了两部分。
首先,我们用SB或者xib快速画出界面,之后和
首先我们创建上半部分视图TopView:
在
之所以要设置
同样的办法创建BottomView
再然后就是分别给
接下来实现这个
还考虑了 取消触摸 或者 手指超出边界 的情况下,让视图自动复原。
现在运行一下就差不多能出效果的。剩下的事情就是细节优化了。这个需要你凭审美微调了。
最后一步我们将效果更加优化,当折叠的时候,我们给
![](http://kittenyang.com/content/images/2015/Jan/-----2015-01-08-23-54-29.png)
我们创建两个渐变图层:
初始化:
然后,在手势的方法里面,根据
这只是这个POP教程的第一篇,代码以可以在这里自取。接下来我会好好学习POP这个动画引擎,然后每次啃完一块骨头就写博客记录,供自己日后学习。
好,吹了那么多牛让我们讲点靠谱的。今天我要手把手教你实现的一个POP动画是这样的:
![](http://kittenyang.com/content/images/2015/Jan/2015-01-09-00_05_10.gif)
首先我们来分析一下。
要让一张照片从中间折过来,如果直接是把一张照片折中而且还要让上下部分显示出不同的阴影,这会非常麻烦。所以,我们使用的技巧是,把一张图切成均等的两部分。然后把切割后的两张图分别作为两个独立的view的image。当它们上下紧贴的时候看上去就像一张完整的图片,但其实是两个视图上下合并的。
有了这个思路,我们接着往下走。
怎么让上半部分view绕着水平中轴线旋转?首先中轴线的位置可以通过重写锚点的位置设置; 绕X轴旋转可以使用
POPBasicAnimation中的
kPOPLayerPositionX;
然后我们会想到,上半视图绕X轴旋转的角度肯定和手指滑动的距离有关。
好了,整体思路就是这样,十分符合情理。唯一需要拐个弯的小技巧就是我们把一张照片割成了两部分。
首先,我们用SB或者xib快速画出界面,之后和
PageView : UIView绑定。
PageView是一个父视图,我们分割开的两个视图就要粘在这个视图上面。
首先我们创建上半部分视图TopView:
在
PageView.m中:
#pragma mark - 上半部分 -(void) addTopView{ self.topView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetMidY(self.bounds))]; //把锚点移到上半视图的底部居中 self.topView.layer.anchorPoint = CGPointMake(0.5, 1.0); //把锚点位置固定在【整个PageView的中心】(可以理解为anchorPoint会吸附到position) self.topView.layer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); //使得topView具有透视效果 self.topView.layer.transform = [self setTransform3D]; self.topView.image = [self cutImageWithID:@"top"]; self.topView.userInteractionEnabled = YES; self.topView.contentMode = UIViewContentModeScaleAspectFill; [self addSubview:_topView]; }
之所以要设置
self.bottomView.layer.transform = [self setTransform3D];是因为如果不设置Transform的这个属性,就看不到纵深的3D效果,换句话说,你不会感觉到折叠的部分在向屏幕靠近。你可以先这么写着然后注释掉这句比较一下就明白我的意思了。
self.bottomView.image = [self cutImageWithID:@"bottom"];用到了
cutImageWithID这个方法,这个方法是我们自己实现的用来把一张图片分割成两部分。具体如下:
-(UIImage *)cutImageWithID:(NSString *)ID{ CGRect rect = CGRectMake(0.f, 0.f, self.image.size.width, self.image.size.height / 2.f); if ([ID isEqualToString:@"bottom"]){ rect.origin.y = self.image.size.height / 2.f; } CGImageRef imgRef = CGImageCreateWithImageInRect(self.image.CGImage, rect); UIImage *cuttedImage = [UIImage imageWithCGImage:imgRef]; CGImageRelease(imgRef); return cuttedImage; }
同样的办法创建BottomView
-(void) addBottomView.
再然后就是分别给
topView和
bottomView增加一个
UIPanGestureRecognizer.很简单,没什么好说的。
-(void)addGestureRecognizer{ UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan1:)]; UITapGestureRecognizer *pokeGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(poke1:)]; [self.topView addGestureRecognizer:panGesture]; [self.topView addGestureRecognizer:pokeGesture]; UIPanGestureRecognizer *panGesture2 =[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan2:)]; UITapGestureRecognizer *pokeGesture2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(poke2:)]; [self.bottomView addGestureRecognizer:panGesture2]; [self.bottomView addGestureRecognizer:pokeGesture2]; }
接下来实现这个
handlePan的动作。 先贴代码:
-(void)pan1:(UIPanGestureRecognizer *)recognizer{ CGPoint location = [recognizer locationInView:self]; //获取手指在PageView中的初始坐标 if (recognizer.state == UIGestureRecognizerStateBegan) { self.initialLocation = location.y; [self bringSubviewToFront:self.topView]; } //如果手指在PageView里面,开始使用POPAnimation if([self isLocation:location InView:self]){ //把一个PI平均分成可以下滑的最大距离份 CGFloat percent = -M_PI / (CGRectGetHeight(self.bounds) - self.initialLocation); //POPAnimation的使用 //创建一个Animation,设置为绕着X轴旋转。还记得我们上面设置的锚点吗?设置为(0.5,0.5)。这时什么意思呢?当我们设置kPOPLayerRotationX(绕X轴旋转),那么x就起作用了,绕x所在轴;kPOPLayerRotationY,y就起作用了,绕y所在轴。 POPBasicAnimation *rotationAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerRotationX]; //给这个animation设值。这个值根据手的滑动而变化,所以值会不断改变。又因为这个方法会实时调用,所以变化的值会实时显示在屏幕上。 rotationAnimation.duration = 0.01;//默认的duration是0.4 rotationAnimation.toValue =@((location.y-self.initialLocation)*percent); //把这个animation加到topView的layer,key只是个识别符。 [self.topView.layer pop_addAnimation:rotationAnimation forKey:@"rotationAnimation"]; //当松手的时候,自动复原 if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { POPSpringAnimation *recoverAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotationX]; recoverAnimation.springBounciness = 18.0f; //弹簧反弹力度 recoverAnimation.dynamicsMass = 2.0f; recoverAnimation.dynamicsTension = 200; recoverAnimation.toValue = @(0); [self.topView.layer pop_addAnimation:recoverAnimation forKey:@"recoverAnimation"]; } } //手指超出边界也自动复原 if (location.y < 0 || (location.y - self.initialLocation)>(CGRectGetHeight(self.bounds))-(self.initialLocation)) { recognizer.enabled = NO; POPSpringAnimation *recoverAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotationX]; recoverAnimation.springBounciness = 18.0f; //弹簧反弹力度 recoverAnimation.dynamicsMass = 2.0f; recoverAnimation.dynamicsTension = 200; recoverAnimation.toValue = @(0); [self.topView.layer pop_addAnimation:recoverAnimation forKey:@"recoverAnimation"]; } recognizer.enabled = YES; }
[self bringSubviewToFront:self.topView];为了让上半个视图旋转超过90度的时候能看到背面的图案,需要吧
topView挪到最上面。
kPOPLayerRotationX的作用是让动画绕着
X轴旋转。
还考虑了 取消触摸 或者 手指超出边界 的情况下,让视图自动复原。
现在运行一下就差不多能出效果的。剩下的事情就是细节优化了。这个需要你凭审美微调了。
最后一步我们将效果更加优化,当折叠的时候,我们给
topView和
bottomView添加阴影。
![](http://kittenyang.com/content/images/2015/Jan/-----2015-01-08-23-54-29.png)
我们创建两个渐变图层:
@property (nonatomic) CAGradientLayer *topShadowLayer; @property (nonatomic) CAGradientLayer *bottomShadowLayer;
初始化:
self.topShadowLayer = [CAGradientLayer layer]; self.topShadowLayer.frame = self.topView.bounds; self.topShadowLayer.colors = @[(id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor]; self.topShadowLayer.opacity = 0; [self.topView.layer addSublayer:self.topShadowLayer];
然后,在手势的方法里面,根据
滑动的距离/PageView总高度的百分比设置layer的透明度,从而达到阴影随着拖动的距离变大而加深。
//添加阴影 if ([[self.topView.layer valueForKeyPath:@"transform.rotation.x"] floatValue] < -M_PI_2) { [CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; self.topShadowLayer.opacity = 0.0; self.bottomShadowLayer.opacity = (location.y-self.initialLocation)/(CGRectGetHeight(self.bounds)-self.initialLocation); [CATransaction commit]; } else { [CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; CGFloat opacity = (location.y-self.initialLocation)/(CGRectGetHeight(self.bounds)-self.initialLocation); self.bottomShadowLayer.opacity = opacity; self.topShadowLayer.opacity = opacity; [CATransaction commit]; }
结尾:
这只是这个POP教程的第一篇,代码以可以在这里自取。接下来我会好好学习POP这个动画引擎,然后每次啃完一块骨头就写博客记录,供自己日后学习。
相关文章推荐
- shell编程笔记
- CentOS系统更换软件安装源
- nginx的负载均衡策略
- nginx安装与应用
- JAVA类加载器和tomcat类加载体系
- linux用yum简单安装apche+mysql+php
- Eclipse中web项目部署至Tomcat步骤
- Q&A
- linux内核device生成流程
- linux环境知识点备忘录
- linux 下mysql卸载
- premature filter
- 静态链接库如何在Linux上运行
- Android系统架构学习与思考
- O_CLOEXEC模式和FD_CLOEXEC选项
- Jenkins+SVN+Maven+Shell 实现项目一键发布
- Linux 文本编辑之Vim/Vi
- centos Linux 统计某个文件夹占用空间大小
- Linux下安装mysql步骤及可能出现的问题
- nginx 301跳转到带www域名方法(不带www访问时重定向到带www域名)