3D菜单卡片

2016-02-24  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读