模仿iOS7 task switcher的卡片动画

作者: 宫城_ | 来源:发表于2016-06-11 17:24 被阅读940次

    原文地址:模仿iOS7 task switcher的卡片动画


    最近看到一个iOS9的task switcher开源实现,但是没有删除功能,就想着干脆做一个模仿iOS7系统的效果,加上删除和重用卡片功能,效果图如下:

    GCCardViewController.gif

    这是代码地址:https://github.com/Yuzeyang/GCCardViewController


    实现上可以使用scrollView或者collectionView去做,这个我是用scrollView去做
    功能点上分为三点:
    1.卡片滑动的效果
    2.卡片重用
    3.卡片删除


    卡片滑动效果

    通过- [scrollViewDidScroll:]代理获取scrollView滑动时的contentOffset值,计算当前contentOffset和原先contentOffset之间的差值diff,再算出进度值progress,以及根据差值diff是否大于0来获取卡片的滑动方向

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
       CGFloat orginContentOffset = self.currentCardIndex*kGCScrollViewWidth;
       CGFloat diff = scrollView.contentOffset.x - orginContentOffset;
       CGFloat progress = fabs(diff)/(kGCViewWidth*0.8);
    
       CardMoveDirection direction = diff > 0 ? CardMoveDirectionLeft : CardMoveDirectionRight;
       for (UIView *card in self.cards) {
    
           [self.cardDelegate updateCard:card withProgress:progress direction:direction];
    
       }
       // 卡片重用
    }
    

    通过调用自己的cardDelegate方法更新卡片的状态

    - (void)updateCard:(UIView *)card withProgress:(CGFloat)progress direction:(CardMoveDirection)direction;
    

    当前卡片不管是左移还是右移,只需要根据progress来更新状态

    card.layer.transform = CATransform3DMakeScale(1 - 0.1 * progress, 1 - 0.1 * progress, 1.0);
    card.layer.opacity = 1 - 0.2*progress;
    

    根据左移还是右移,来决定改变当前卡片下一张还是上一张卡片的状态

    NSInteger transCardTag = direction == CardMoveDirectionLeft ? [self.cardScrollView currentCard] + 1 : [self.cardScrollView currentCard] - 1;
    
    card.layer.transform = CATransform3DMakeScale(0.9 + 0.1*progress, 0.9 + 0.1*progress, 1.0);
    
    card.layer.opacity = 0.8 + 0.2*progress;
    

    卡片重用

    由于页面上只显示三张卡片,所以要重用卡片的话,我们需要初始化四张卡片,类似于tableViewCell的重用处理一样,当第一张卡片离开屏幕显示之后,将第一张卡片移到最后一张卡片的后面,反之,同理
    在- [scrollViewDidScroll:]里面,根据contentOffset变化的绝对值大于scrollView宽度的80%时,对卡片进行重用,以及改变当前的index

    if (fabs(diff) >= kGCScrollViewWidth*0.8) {
           self.currentCardIndex = direction == CardMoveDirectionLeft ? self.currentCardIndex + 1 : self.currentCardIndex - 1;
           [self reuseCardWithMoveDirection:direction];
    
    }
    

    在重用之前,并不是所有位置都需要重用,在index(index从0开始计算)小于2或者index大于总卡片数量-3的时候,才需要重用,左移时,取出cards数组里面第一个card,将card移到最后一个card后面,改变它的center就可以,右移时,取出最后一个card,移到第一个card前面

    - (void)reuseCardWithMoveDirection:(CardMoveDirection)moveDirection {
       BOOL isLeft = moveDirection == CardMoveDirectionLeft;
       UIView *card = nil;
       if (isLeft) {
           if (self.currentCardIndex > self.totalNumberOfCards - 3 || self.currentCardIndex < 2) {
               return;
           }
           card = [self.cards objectAtIndex:0];
           card.tag+=4;
       } else {
           if (self.currentCardIndex > self.totalNumberOfCards - 4 ||
               self.currentCardIndex < 1) {
               return;
           }
           card = [self.cards objectAtIndex:3];
           card.tag-=4;
       }
       card.center = [self centerForCardWithIndex:card.tag];
    
       [self.cardDataSource cardReuseView:card atIndex:card.tag];
    
       [self ascendingSortCards];
    }
    

    并且调用- [cardReuseView:atIndex:]对重用的卡片改变数据源,最后按tag值升序排序

    - (UIView *)cardReuseView:(UIView *)reuseView atIndex:(NSInteger)index {
       if (reuseView) {
           // you can set new style
           return reuseView;
       }
       
       UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kGCCardWidth * 0.9, kGCCardHeight)];
       card.layer.backgroundColor = [UIColor whiteColor].CGColor;
       card.layer.cornerRadius = 4;
       card.layer.masksToBounds = YES;
       
       return card;
    
    }
    

    卡片删除

    卡片删除是一个可选功能,通过设置canDeleteCard来添加手势

    @property (nonatomic, assign) BOOL canDeleteCard;
    
    if (self.canDeleteCard) {
       UIPanGestureRecognizer *deleteGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(deleteCard:)];
    
       deleteGesture.minimumNumberOfTouches = 1;
    
       deleteGesture.maximumNumberOfTouches = 1;
    
       deleteGesture.delegate = self;
    
       [card addGestureRecognizer:deleteGesture];
    
    }
    

    由于卡片有拖动手势和scrollView也有拖动手势,这两个手势会出现冲突,所以我们需要根据手势的方向来判断到底应该是作用于卡片上还是scrollView上

    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
       if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
           CGPoint translatedPoint = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:gestureRecognizer.view];
           if (fabs(translatedPoint.y) > fabs(translatedPoint.x)) {
               return YES;
           }
       }
       return NO;
    
    }
    

    在调用卡片删除手势时,向下拖动时不做删除,向上拖动屏幕高度的一半后,删除卡片,并且重用删除后的卡片,这部分重用相对比较复杂
    当卡片小于等于四张时,我们直接移除当前的卡片
    -> 如果当前卡片index为0时 -> 右边的卡片左移 -> 右边卡片的tag均减1
    -> 如果当前卡片index为最后一张时 -> 左边的卡片右移 -> 左边卡片的tag不变
    -> 如果当前卡片index为中间时 -> 右边的卡片左移 -> 右边卡片的tag均减1
    最后按升序排序

    if (self.totalNumberOfCards <= 4) {
       [(UIView *)[self.cards objectAtIndex:index] removeFromSuperview];
    
       [self resetTagFromIndex:index];
    
       [self.cards removeObjectAtIndex:index];
    
       [self ascendingSortCards];
    
       return;
    
    }
    
    - (void)resetTagFromIndex:(NSInteger)index {
       [self.cards enumerateObjectsUsingBlock:^(UIView *card, NSUInteger idx, BOOL * _Nonnull stop) {
           if ((NSInteger)idx > index) {
               card.tag-=1;
               [UIView animateWithDuration:0.3 animations:^{
                   card.center = [self centerForCardWithIndex:card.tag];
               }];
           }
       }];
    
    }
    

    当卡片超过四张时,我们需要重用删除的卡片
    -> 如果当前卡片index为0时 -> 卡片的tag值加4 -> 右边的卡片左移 -> 右边卡片的tag均减1
    -> 如果当前卡片index为最后一张时 -> 卡片的tag值减4 -> 左边的卡片右移 -> 左边卡片的tag不变
    -> 如果当前卡片index为中间时 -> 以四个卡片为一组,获取第一个卡片和最后一个卡片的tag值 -> 判读最后一个卡片是否是最后一张卡片 -> 如果是,则将卡片移到第一张卡片的左边,如果不是,则将卡片移到最后一张卡片的右边 -> 右边的卡片左移 -> 右边卡片的tag均减1
    最后按升序排序

    UIView *card = [self.cards objectAtIndex:index];
    NSInteger fromIndex = index;
    if (index == 0) {
       card.tag+=4;
       fromIndex = index - 1;
    } else if (index == 3) {
       card.tag-=4;
    } else {
       NSInteger lastTag = ((UIView *)[self.cards lastObject]).tag;
       NSInteger firstTag = ((UIView *)[self.cards firstObject]).tag;
       if (lastTag == self.totalNumberOfCards - 1) {
    
           card.tag = firstTag - 1;
    
       } else {
           card.tag = lastTag + 1;
           fromIndex = index - 1;
       }
    }
    card.center = [self centerForCardWithIndex:card.tag];
    [self ascendingSortCards];
    [self resetTagFromIndex:fromIndex];
    [self.cardDataSource cardReuseView:card atIndex:card.tag];
    

    最后只要改变scrollView的contentSize和卡片状态即可~

    [UIView animateWithDuration:0.3 animations:^{
    
       [self.scrollView setContentSize:CGSizeMake(kGCScrollViewWidth*self.totalNumberOfCards, kGCViewHeight)];
       for (UIView *card in self.cards) {
           [self.cardDelegate updateCard:card withProgress:1 direction:CardMoveDirectionNone];
       }
    
    }];
    

    相关文章

      网友评论

        本文标题:模仿iOS7 task switcher的卡片动画

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