美文网首页
3D菜单卡片

3D菜单卡片

作者: 怀心逝水 | 来源:发表于2016-02-24 18:37 被阅读148次
    u=2294641597,1984868856&fm=206&gp=0.jpg CardAnimation.gif

    还是先看效果图。

    这个动态效果我没有用ios 原生的Animation,而是用popAnimation,当然在用之前得用pods把pop下载到你的工程文件中,这个步骤网上有很多资料,按照那里面说的安装就可以了。

    我的这个动画是直接封装成一个View的。在controller中只需要一句代码就可以了。

    CGRect cardViewFrame = self.view.bounds;
            _cardView = [[DynamicCardsView alloc] initWithFrame:cardViewFrame 
                                      subImageArray:self.imageArr allImageArr:self.allImageArr];
    

    至于这里面的imageArr是你要在这个View上显示的图片(太多的图片可能太拥挤了)而allImageArr是所有的图片。

    下面就看看封装的那个View中是怎么写的。

    准备工作:

    利用接口中传过来的imageArray ,在View中初始化两类imageArray,

    - (instancetype)initWithFrame:(CGRect)frame
                    subImageArray:(NSArray *)subArr
                      allImageArr:(NSArray *)allArr {
        
        self = [super initWithFrame:frame];
        if (self) {
            self.subImageArray = subArr;
            self.allImageArray = allArr;
            
            self.subImageCount = subArr.count;
            [self getCustomCards];
            [self makeCards3D];
        }
        return self;
    }
    

    拿到图片后,就有利用这些图片去初始化我们的卡片CustomCard并且把这些卡片对象放进一个数组中。

    - (void)getCustomCards {
        
        NSMutableArray *cardsImg = [NSMutableArray array];
        [self.subImageArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            CustomCard *card = [[CustomCard alloc] initWithFrame:CGRectMake(0, 0, 250, 200) image:[UIImage imageNamed:obj]];
            card.tag = 100 + idx;
            [self addSubview:card];
            card.center = self.center;
            [cardsImg addObject:card];
        }];
        self.cardsImg = [cardsImg copy];
    }
    

    这儿我做了第二次封装,就是对这个卡片的形状(白色边框,或者里面的布局)当然它也是一个View的子类

    - (id)initWithFrame:(CGRect)frame image:(UIImage *)image {
        
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor whiteColor];
            _image = image;
            [self setup];
            [self addSubview:self.cardImageView];
        }
        return self;
    }
    
    - (void)setup {
        
        self.cardImageView = [UIImageView new];
        self.cardImageView.left = 5;
        self.cardImageView.top = 5;
        self.cardImageView.width = self.width - 5*2;
        self.cardImageView.height = self.height - 5*2;
        self.cardImageView.image = self.image;
        self.cardImageView.contentMode = UIViewContentModeScaleToFill;
    }
    

    当然你也可以在这里给每个card添加手势。不过我是在外面一层添加的。

    - (void)makeCards3D {
        
        [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            CGFloat cardScale = 0.1*idx + 0.8;
            obj.transform = CGAffineTransformScale(obj.transform, cardScale, cardScale);
            
            CGFloat cardTranslate = 0;
            if (idx == 0) {
                cardTranslate = 70;
            }else {
                cardTranslate = (self.cardsImg.count - idx) * 22;
            }
            if (idx == self.cardsImg.count - 1) {
                [self addGesOnTopCardView:obj];
            }
            obj.transform = CGAffineTransformTranslate(obj.transform, 0, cardTranslate);
        }];
    }
    

    这个方法是在View上显示三张图片由大到小,并且只在最外面的那个图片上添加手势。(这样就点击其他图片时无响应)。
    然后就是给最上面那张图片添加手势。

    - (void)addGesOnTopCardView:(CustomCard *)cardView {
        
        UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                                 action:@selector(cardViewDidPan:)];
        [cardView addGestureRecognizer:panGes];
        
        UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                 action:@selector(cardViewDidTap:)];
        [cardView addGestureRecognizer:tapGes];
    }
    

    这里我是添加两个手势,一个是滑动手势,一个是点击手势。
    接着就是一大波的代码了。

    - (void)cardViewDidPan:(UIPanGestureRecognizer *)panGes {
        
        CGPoint location = [panGes locationInView:panGes.view];
        CGPoint velocity = [panGes velocityInView:panGes.view];
        CGPoint translate = [panGes translationInView:panGes.view];
        if (panGes.state == UIGestureRecognizerStateBegan) {
            self.initialLocation = location.x;
        }
        
        if (translate.x < 0 && panGes.view.centerX == self.centerX) {
            [self springAnimationToVale:CGPointMake(0, self.centerY)
                               withView:panGes.view velocity:velocity];
            [self bottomCardsViewSpreadAnimationwithVelocity:velocity];
            self.isSpreadCards = YES;
            
        }else if (translate.x >= 0) {
            if (panGes.view.centerX == 0) {
                [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    [self eachCardViewDidRotate:obj rotate:0];
                }];
                if (self.isTapCardView) {
                    [self makeCards3D];
                    self.isTapCardView = NO;
                }
                [self springAnimationToVale:CGPointMake(self.centerX, self.centerY)
                                   withView:panGes.view velocity:velocity];
                [self bottomCardsViewShrinkAnimationwithVelocity:velocity];
                self.isSpreadCards = NO;
            }else if (panGes.view.centerX == self.centerX) {
                [self dismissTopCardView:CGPointMake(self.width + panGes.view.width/2.0,
                                                     self.centerY)
                                withView:panGes.view];
                [self bottomCardsViewRecoverScaleWithVelocity:velocity complete:^(BOOL finished){
                }];
            }
        }
    }
    

    可以看到这里面有三种情况:
    1.是直接向左滑动时,三种卡片各自的运动轨迹,也就会展开的。(self.isSpreadCards = YES,这个时候说明可以点击图片了。)
    2.在图片展开的情况下往右滑动,这个时候卡片会呈叠起状态。并且卡片不能被点击。
    3.直接向右滑动时,最外面一个图片会消失而在图片在底部会增加一张图片。
    上面那个方法只是将卡片的滑动和点击进行逻辑判断。

    接着就是运用pop中的animation。

    - (void)springAnimationToVale:(CGPoint)point withView:(UIView *)view velocity:(CGPoint)velocity{
        
        POPSpringAnimation *swipeAni = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
        swipeAni.velocity = [NSValue valueWithCGPoint:velocity];
        swipeAni.toValue = [NSValue valueWithCGPoint:point];
        [view.layer pop_addAnimation:swipeAni forKey:@"swipeAnimtion"];
    }
    

    这个方法中主要运用pop的弹簧动画,在x轴上移动也就是在展开过程中每张图片的animation

    - (void)bottomCardsViewSpreadAnimationwithVelocity:(CGPoint)velocity{
        
        [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (idx == 1) {
                [self springAnimationToVale:CGPointMake(self.centerX - 20,self.centerY)
                                   withView:obj velocity:velocity];
            }else if (idx == 0) {
                [self springAnimationToVale:CGPointMake(self.width - 40,self.centerY)
                                   withView:obj velocity:velocity];
            }
        }];
    }
    

    这个方法中是第二张和第三张图片将会移动的animation。
    这样上面的第一种滑动就实现了。

    接着就是在展开的情况下点击了,或者是向右滑动。

    - (void)cardViewDidTap:(UITapGestureRecognizer *)tapGes {
        
        if (!self.isSpreadCards) {
            return;
        }
        self.isTapCardView = YES;
        [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [self eachCardViewDidRotate:obj rotate:-M_PI_4];
        }];
        
    }
    

    向右滑动

    - (void)bottomCardsViewShrinkAnimationwithVelocity:(CGPoint)velocity{
        
        [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (idx == 1) {
                [self springAnimationToVale:CGPointMake(self.centerX,
                                                        self.centerY)
                                   withView:obj velocity:velocity];
            }else if (idx == 0) {
                [self springAnimationToVale:CGPointMake(self.centerX,
                                                        self.centerY)
                                   withView:obj velocity:velocity];
            }
        }];
    }
    

    这里会设置卡片是在点击状态下。(因为展开后的卡片接下来的操作可能会向→滑,也可能点击)所以得有旗子进行标识。

    - (void)eachCardViewDidRotate:(CustomCard *)cardView rotate:(CGFloat)angleValue{
        
        POPSpringAnimation *rotateAni = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotationY];
        [cardView.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
        rotateAni.springBounciness = 18.0f;
        rotateAni.dynamicsMass = 2.0f;
        rotateAni.dynamicsTension = 200;
        rotateAni.toValue = @(angleValue);
        [cardView.layer pop_addAnimation:rotateAni forKey:@"rotationAnimation"];
    }
    

    这个方法会将每个卡片在y轴进行角度翻转

    最后就是第三种情况了。

    - (void)dismissTopCardView:(CGPoint)point withView:(UIView *)view {
        
        [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8
              initialSpringVelocity:10 options:0 animations:^{
                  view.center = point;
              } completion:^(BOOL finished) {
                  [view removeFromSuperview];
                  [self adjustImageArr];
                  [self addCardViewOnBottomest];
              }];
    }
    

    首先让最上面的那张图片滑动消失。同时就得调整图片数组中的数据了。
    接着让本来第二张,第三张图片的scale变成第一张和第二张的scale

    - (void)bottomCardsViewRecoverScaleWithVelocity:(CGPoint)velocity complete:(void (^)(BOOL finished))completion {
        
        [self.cardsImg enumerateObjectsUsingBlock:^(CustomCard *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (idx == 1) {
                [self recoverScaleWithScale:1.0/0.9 translate:-20 cardView:obj];
            }else if (idx == 0) {
                [self recoverScaleWithScale:1.0/0.9 translate:-20 cardView:obj];
            }
        }];
    }
    
    - (void)recoverScaleWithScale:(CGFloat)scale translate:(CGFloat)translate cardView:(UIView *)view {
        
        [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8
              initialSpringVelocity:10 options:0 animations:^{
                  view.transform = CGAffineTransformTranslate(view.transform, 0, translate);
                  view.transform = CGAffineTransformScale(view.transform, scale, scale);
              } completion:nil];
        
    }
    

    调整图片数组

    - (void)adjustImageArr {
        
        id addObj = [self.allImageArray objectAtIndex:self.subImageCount];
        self.subImageCount = self.subImageCount + 1;
        if (self.subImageCount == self.allImageArray.count) {
            self.subImageCount = 0;
        }
        NSMutableArray *adjustArr = [self.subImageArray mutableCopy];
        [adjustArr lastObject];
        [adjustArr insertObject:addObj atIndex:0];
        self.subImageArray = [adjustArr mutableCopy];
    }
    

    也就是把所有图片数组中要显示的图片添加到子图片数组中并且在0的位置上插入。
    接着要做的就是把新插入的图片初始化成一个card的对象。
    并且生成新的card数组对象.

    - (void)addCardViewOnBottomest {
        
        NSMutableArray *adjustCards = [self.cardsImg mutableCopy];
        [adjustCards removeLastObject];
        [self.subImageArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (idx == 0) {
                CustomCard *bottomCard = [adjustCards firstObject];
                CustomCard *card = [[CustomCard alloc] initWithFrame:CGRectMake(0, 0, 250, 200) image:[UIImage imageNamed:obj]];
                [UIView animateWithDuration:0.2 delay:0 usingSpringWithDamping:0.8
                      initialSpringVelocity:10 options:0 animations:^{
                          card.center = self.center;
                          [self recoverScaleWithScale:0.8 translate:55 cardView:card];
                      } completion:^(BOOL finished) {
                          [self insertSubview:card belowSubview:bottomCard];
                      }];
                [adjustCards insertObject:card atIndex:0];
            }else if (idx == self.subImageArray.count - 1) {
                CustomCard *topCard = [adjustCards lastObject];
                [self addGesOnTopCardView:topCard];
            }
            self.cardsImg = [adjustCards copy];
        }];
    }
    

    这样就可以周而复始的滑动了。
    这个工程代码我会放到我的github上。因为工程代码中还有一些动画,地址我会放在最后一篇动画中。

    u=2278907624,1794819904&fm=206&gp=0.jpg

    相关文章

      网友评论

          本文标题:3D菜单卡片

          本文链接:https://www.haomeiwen.com/subject/eixkkttx.html