干货系列之实现City Guides的动画效果(一)

点此下载源码下载:源码(会持续更新,欢迎star。保证炫酷,童叟无欺!)

City Guides App.jpg

dribbble的设计师的作品中了解到City Guides上非常优秀的动画效果。感叹设计师很棒的设计的同时,小编也在心里默想这些动画是怎么实现的。于是从App Store上下载了APP,使用然后仔细研究后便有了此篇文章。

City Guides中几乎所有的界面展示与交互方式都是有动画效果的。小编也就分几部分来实现动画效果。

这一篇要实现的动画效果如下:

第一个动画效果:

ZFCityGuides-One.gif

第二个动画效果:

ZFCityGuides-two.gif

本篇文章只讲解实现思路,具体的可以参见源码。

动画效果一

第一个动画效果,实际上包括三个部分动画效果。选中动画效果动画转场和切换动画效果。本篇文章中所有的动画,使用POP和Core Animation实现。

选中动画效果

此动画实现相对简单一些,中间部分是使用的UICollectionView,创建了四个cell。在可视的cell中实现被选中的cell放大,其余可视的cell缩放的同时透明度减少。使用POP实现的方法如下:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    
    
    UICollectionViewCell *selectedCell = (UICollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];

    
    for (UICollectionViewCell *cell in collectionView.visibleCells) {
        
        if ([cell isEqual:selectedCell]) {
          
            //选中的cell的放大
            POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
            scaleAnimation.duration = 0.5;
            scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1.05, 1.05)];
            [cell.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
            
            POPBasicAnimation *alphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
            alphaAnimation.duration = 0.5;
            alphaAnimation.toValue = @1.0;
            [cell pop_addAnimation:alphaAnimation forKey:nil];
            
            [alphaAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {

//                if (finished) {
//                    
//                    ZFMainTabBarController *mainTabBarVC = [[ZFMainTabBarController alloc] init];
//                    mainTabBarVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
//                    mainTabBarVC.transitioningDelegate = self;
//                    
//                    self.interactionViewController = [ZFInteractiveTransition new];
//                    [self presentViewController:mainTabBarVC animated:YES completion:NULL];
//                }

            }];
        }
        else{
            
            //未选中的可视cell的缩放
            POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
            scaleAnimation.duration = 0.5;
            scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(0.95, 0.95)];
            [cell.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
            
            POPBasicAnimation *alphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
            alphaAnimation.duration = 0.5;
            alphaAnimation.toValue = @0.7;
            [cell pop_addAnimation:alphaAnimation forKey:nil];
            
        }
    }

}

此部分实现的效果图:

ZFCityGuides-1.gif

动画转场

此部分的转场过渡动画效果,参考小编上篇文章讲解的自定义转场。里面涉及到手势交互,下一篇文章再做详细讲解。

切换效果动画

实际上是点击最上面的两个button后,对应的视图动画效果。

切换到SLIDES后,UICollectionView整个视图向左移,移出当前屏幕。与此同时可视的UICollectionViewCell随着变化,而且每个cell的变化有区别。区别是:移出的时候,第二个cell最后消失在视线里(偏移量最小),第四个cell旋转幅度较大(角度最大)。
实现的方式:

-(void)showSlideLayout:(UIButton *)sender{
    

    if (_SliderButton.selected) {
        return;
    }

    [_scrollView setHidden:NO];
    
    _SliderButton.selected = YES;
    _GridButton.selected = NO;
    _SliderButton.userInteractionEnabled = NO;
    [self canEnableClick];
    _backgroundLayer.frame = sender.frame;
    
    int i = 0;
    for (UICollectionViewCell *cell in _cityCollectView.visibleCells) {
        
        NSArray *translationArray = @[@-300, @-200, @-600, @-600];
        NSArray *angles = @[@(-15* M_PI/180), @(-30* M_PI/180), @(-15* M_PI/180), @(-60* M_PI/180)];
        NSArray *scaleArray = @[@0, @0, @0, @-5];

        //未选中的可视cell的缩放
        
        POPBasicAnimation *rotationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
        rotationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerRotation];
        rotationAnimation.duration = duration;
        rotationAnimation.fromValue = @(0);
        rotationAnimation.toValue = angles[i];
        
        POPBasicAnimation *translationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
        translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerTranslationX];
        translationAnimation.duration = duration;
        translationAnimation.fromValue = @(0);
        translationAnimation.toValue = translationArray[i];
        
        POPBasicAnimation *zPositionAnimation = [POPBasicAnimation easeInEaseOutAnimation];
        zPositionAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerZPosition];
        zPositionAnimation.duration = duration;
        zPositionAnimation.toValue = scaleArray[i];
        
        [cell.layer pop_addAnimation:rotationAnimation forKey:@"rotationAnimation"];
        [cell.layer pop_addAnimation:translationAnimation forKey:@"translationAnimation"];
        [cell.layer pop_addAnimation:zPositionAnimation forKey:@"zPositionAnimation"];
        i++;
        
    }
    
    // collectionView 移动
    CGRect fromFrame = _cityCollectView.frame;
    CGRect toFrome = fromFrame;
    
    fromFrame.origin.x -= fromFrame.size.width;
    _cityCollectView.frame = fromFrame;
    
    POPBasicAnimation *frameAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame];
    frameAnimation.name = @"showSliderView";
    frameAnimation.duration = duration;
    frameAnimation.fromValue = [NSValue valueWithCGRect:toFrome];
    frameAnimation.toValue = [NSValue valueWithCGRect:fromFrame];
    frameAnimation.delegate = self;

    [_cityCollectView pop_addAnimation:frameAnimation forKey:@"frameAnimation"];
    
    [frameAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
        
        if (finished) {
            [_cityCollectView setHidden:YES];
        }
        
    }];

}


切换到GRID后,UICollectionView整个视图向右移,渐入到当前屏幕。UICollectionViewCell变化与上面有些不一样。第3个cell 和第4个cell最后完成动画,其动画变化较大。

-(void)showGridLayout:(UIButton *)sender{
    
    if (_GridButton.selected) {
        return;
    }
    
    [_cityCollectView setHidden:NO];
    _backgroundLayer.frame = sender.frame;
    
    _SliderButton.selected = NO;
    _GridButton.selected = YES;
    _GridButton.userInteractionEnabled = NO;
    [self canEnableClick];
    
    int i = 0;
    for (UICollectionViewCell *cell in _cityCollectView.visibleCells) {
        
        NSArray *translationArray = @[@-5, @0, @-300, @-100];
        NSArray *angles = @[@(-2* M_PI/180), @(-5* M_PI/180), @(-30* M_PI/180), @(-10* M_PI/180)];
        //未选中的可视cell的缩放

        POPBasicAnimation *rotationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
        rotationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerRotation];
        rotationAnimation.duration = duration;
        rotationAnimation.fromValue = angles[i];
        rotationAnimation.toValue = @(0);
        
        
        
        POPBasicAnimation *translationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
        translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerTranslationX];
        translationAnimation.duration = duration;
        translationAnimation.fromValue = translationArray[i];
        translationAnimation.toValue = @(0);

        [cell.layer pop_addAnimation:rotationAnimation forKey:@"rotationAnimation"];
        [cell.layer pop_addAnimation:translationAnimation forKey:@"translationAnimation"];
        i++;
   
    }
    
    // collectionView 移动
    CGRect fromFrame = _cityCollectView.frame;
    CGRect toFrome = fromFrame;
    toFrome.origin.x = 0;
    
    POPBasicAnimation *frameAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame];
    frameAnimation.name = @"showGridView";
    frameAnimation.duration = duration;
    frameAnimation.fromValue = [NSValue valueWithCGRect:fromFrame];
    frameAnimation.toValue = [NSValue valueWithCGRect:toFrome];
    frameAnimation.delegate = self;
    [_cityCollectView pop_addAnimation:frameAnimation forKey:@"frameAnimation"];
    
    [frameAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
    
        if (finished) {
            [_scrollView setHidden:YES];
        }

    }];

}

以上两个动画完成的过程中,仔细观察SLIDES下对应的视图,UIScrollView有一些细微的变化。在Y方向上有一定的偏移量的变化。是根据UICollectionView移出的位置变化而改变。使用POPAnimationDelegate的方法,根据当前执行动画决定在Y方向偏移量是逐步增加或减少。

- (void)pop_animationDidApply:(POPAnimation *)anim
{
//    NSLog(@"%@",anim.name );
    CGRect currentValue = [[anim valueForKey:@"currentValue"] CGRectValue];

    if ([anim.name isEqualToString:@"showSliderView"]) {
        
        CGRect frame = _scrollView.frame;
        CGFloat distanceY = 30 *(1 -fabs(currentValue.origin.x /_scrollView.frame.size.width));
        
        frame.origin.x = _scrollView.frame.size.width  + currentValue.origin.x;
        frame.origin.y =  CGRectGetMaxY(_SliderButton.frame) + 20.  +  distanceY  ;
        
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.0];
        _scrollView.frame = frame;
        [UIView commitAnimations];
    }
    else if([anim.name isEqualToString:@"showGridView"]){

        CGRect frame = _scrollView.frame;
        CGFloat distanceY = 30 * (1 -  fabs(currentValue.origin.x /_scrollView.frame.size.width));
        
        frame.origin.x = _scrollView.frame.size.width  + currentValue.origin.x;
        frame.origin.y =  CGRectGetMaxY(_SliderButton.frame) + 20. +  distanceY ;
        
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.0];
        _scrollView.frame = frame;

        [UIView commitAnimations];
    }
}

具体效果参见下图:

ZFCityGuides-2.gif

动画效果二

此部分动画是使用UIScrollView来实现,同样可以使用UICollectionView来实现。此处是UICollectionView实现。本篇文章使用继承UIScrollView的类来实现。只需要以下几行代码即可。是根据UIScrollView的子视图偏移量的变化实现。参考以下代码


#import "ZFTransformScrollView.h"

@implementation ZFTransformScrollView


-(void)layoutSubviews{
    
    [super layoutSubviews];
    
    CGFloat contentOffsetX = self.contentOffset.x;

    for(UIView *view in self.subviews){
        
        CATransform3D t1 = CATransform3DIdentity;
        view.layer.transform = t1;
        //计算每个cell的偏移量
        CGFloat distanceFromCenterX = view.frame.origin.x - contentOffsetX;
  
        CGFloat offsetRatio = distanceFromCenterX / CGRectGetWidth(self.frame);
        CGFloat angle = offsetRatio * 30;
        //前半部分
        if (offsetRatio < 0) {
            //offsetRation 为负
            //沿y轴向右转 沿着y的正方向看是逆时针
            CATransform3D t2 = CATransform3DMakeRotation(DEGREES_TO_RADIANS(-angle), 0, 1,0 );
            //沿x轴向里转 沿着x的正方向看是逆时针
            CATransform3D t3 = CATransform3DRotate(t2, DEGREES_TO_RADIANS(-5 * offsetRatio) , 1, 0, 0);
            //沿y轴向下移动 距离正
            CATransform3D t4 = CATransform3DTranslate(t3, 0, -35 *offsetRatio, 0);
            //实现透视投影 默认是正交投影 以CGPointMake(0, 0)为观察点 3D效果更明显
            view.layer.transform = CATransform3DPerspect(t4, CGPointMake(0, 0), 500);
        }
        else{
            //给个起始位置 在正常位置以下以左的某段距离
            t1 = CATransform3DMakeTranslation(60 * offsetRatio * 1.5, 100 * offsetRatio * 1.5, 0);
            //沿y轴向左转 沿着y的正方向看是顺时针
            CATransform3D t2 = CATransform3DRotate(t1,DEGREES_TO_RADIANS(-angle), 0, 1,0 );
            //沿x轴向里转 沿着x的正方向看是逆时针
            CATransform3D t3 = CATransform3DRotate(t2, DEGREES_TO_RADIANS(5 * offsetRatio), 1, 0, 0);
            //沿y轴向上移动 距离正
            CATransform3D t4 = CATransform3DTranslate(t3, 0, -35 *offsetRatio, 0);
            //实现透视投影 默认是正交投影 以CGPointMake(0, 0)为观察点 3D效果更明显
            view.layer.transform = CATransform3DPerspect(t4, CGPointMake(0, 0), 500);
        }

    }
}

@end

其中CATransform3DPerspect实现方法和使用原理可以参考此篇文章iOS 3D UI---CALayer的transform扩展

CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
    CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
    CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
    CATransform3D scale = CATransform3DIdentity;
    scale.m34 = -1.0f/disZ;
    return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}

CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
    return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}

后续实现内容,请持续关注小编。

ZFCityGuides3.gif

点此下载源码下载:源码(会持续更新,欢迎star)

扩展阅读

干货系列之实现City Guides的动画效果(二)

iOS 3D UI---CALayer的transform扩展

干货系列之实战详解自定义转场动画

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容