IOS 类似探探卡片滑动效果

IOS 类似探探卡片滑动效果

之前写的类似的效果,现在整理一下demo,方便之后可能会用到。

卡片要实现
1、实现左右滑动
2、滑动之后删除或者还原

首先有数据Cards数组,以及CardViews数组,一次性只显示三个卡片,当滑动删除第一个后,从cards数组中取出数据,创建一个新的CardView加到界面上以及加入到CardViews数组中。

首先先创建三个卡片


- (void)layoutItemSubviews {
    NSInteger index = 0;
    for (UIView *subView in self.subviews) {
        if ([subView isKindOfClass:[INUserCardItemView class]]) {
            
            //第一个卡片,不缩小,第二个卡片缩小90%
            CGFloat scale = (1-kScalePrecent*(self.cardViews.count - 1 - index));
            NSLog(@"scale : %f",scale);
            [subView setScaleWithPercent:scale duration:0.0001 completion:nil];
            CGPoint targetCenter = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2+kCardPadding*(self.cardViews.count-index-1)+kCardOffsetY*(self.cardViews.count-1-index));
            
            NSLog(@"targetCenter : (%f,%f)",targetCenter.x,targetCenter.y);
            
            [subView setCenter:targetCenter duration:0.1f completion:nil];
            index ++;
        }
    }
}

- (void)setCards:(NSMutableArray *)cards {
    _cards = cards;
    [self setupCardViews];
}

- (void)setupCardViews {
    for (UIView *subView in self.subviews) {
        if ([subView isKindOfClass:[INUserCardItemView class]]) {
            [subView removeFromSuperview];
        }
    }
    
    //显示的卡片
    for (NSInteger index = 0; index < self.cards.count; index ++) {
        // 数据
        id object = [self.cards objectAtIndex:index];
        
        INUserCardItemView *cardView = [[INUserCardItemView alloc] initWithFrame:CGRectZero];
        cardView.frame = CGRectMake(kCardPadding, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds) - 2*kCardPadding,  CGRectGetWidth([UIScreen mainScreen].bounds) - 2*kCardPadding);

        cardView.data = object;
        
        [self insertSubview:cardView atIndex:0];
        [self.cardViews insertObject:cardView atIndex:0];
        
        if (index >= 2) {
            break;
        }
    }
    [self layoutItemSubviews];
}

在通过手势控制卡片滑动的效果,加入拖动手势UIPanGestureRecognizer

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor colorWithHexString:@"efeff4"];
        
        self.cardViews = [NSMutableArray arrayWithCapacity:0];
        
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureHandle:)];
        panGesture.minimumNumberOfTouches = 1;
        panGesture.maximumNumberOfTouches = 1;
        [self addGestureRecognizer:panGesture];
    }
    return self;
}

实现拖动的过程中的移动及旋转

- (void)panGestureHandle:(UIPanGestureRecognizer *)pan {
    if (self.cardViews.count > 0) {
        INUserCardItemView *card = self.cardViews[self.cardViews.count-1];
        INUserCardItemView *nextCard = nil;
        if (self.cardViews.count >= 2) {
            nextCard = self.cardViews[self.cardViews.count-2];
        }
        if (pan.state == UIGestureRecognizerStateBegan) {
            self.transitionCardCenter = card.center;
        } else if (pan.state == UIGestureRecognizerStateChanged) {
            CGPoint transLcation = [pan translationInView:self];
            NSLog(@"transLcation:(%f,%f)",transLcation.x,transLcation.y);
            
            CGFloat XOffPercent = (card.center.x-CGRectGetWidth(self.bounds)/2)/(CGRectGetWidth(self.bounds)/2);
            
            CGPoint targetCenter = CGPointMake(self.transitionCardCenter.x+transLcation.x, self.transitionCardCenter.y+transLcation.y);
            [card setCenter:targetCenter duration:0.0 completion:nil];
            
            [nextCard setScaleWithPercent:1 duration:0.1f completion:nil];
            
            CGFloat rotation = M_PI_2/16*XOffPercent;
            //            [card setRorationWithAngle:rotation duration:0.0 completion:nil];
            
            CGFloat kInterval = 80.0;
            //卡片中心点在界面的中心的左右30.0内是,不显示关注或跳过icon
            if (card.center.x < (self.center.x - kInterval)) {
                //显示跳过icon
                // [card cardMoveLeft];
            } else if (card.center.x > (self.center.x + kInterval)){
                //显示关注icon
                //[card cardMoveRight];
            } else {
                //不显示关注或跳过icon
                //[card restoreCardIcon];
            }
            [self updateCardStatus:YES];
            
        } else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateFailed) {
            
            CGFloat kWdith = CGRectGetWidth(self.frame)/2;
            NSLog(@"card.center.x:%f",card.center.x);
            
            //卡片向右滑动,滑动到3/4时候移除
            if (card.center.x > (kWdith + 10.0)) {
                //向右滑动,关注该用户
                
                CGPoint targetCenter = CGPointMake(CGRectGetWidth(self.bounds)+CGRectGetWidth(card.frame)/2, card.center.y + CGRectGetHeight(card.frame)/3.0);
                CGFloat XOffPercent = (targetCenter.x-CGRectGetWidth(self.bounds)/2)/(CGRectGetWidth(self.bounds)/2);
                
                CGFloat rotation = M_PI_2/16*XOffPercent;
                
                [card setCenter:targetCenter duration:0.25f completion:nil];
                [card setRorationWithAngle:rotation duration:0.25f completion:nil];
                
                [self performSelector:@selector(cardRightRemoveDismiss:) withObject:card afterDelay:0.1f];
                
                return;
            }
            
            //卡片向左滑动
            if (card.center.x < (kWdith - 10.0)) {
                CGPoint targetCenter = CGPointMake(-CGRectGetWidth(card.frame)/2, card.center.y + CGRectGetHeight(card.frame)/3.0);
                CGFloat XOffPercent = (targetCenter.x-CGRectGetWidth(self.bounds)/2)/(CGRectGetWidth(self.bounds)/2);
                
                CGFloat rotation = M_PI_2/16*XOffPercent;
                
                [card setCenter:targetCenter duration:0.25f completion:nil];
                [card setRorationWithAngle:rotation duration:0.25f completion:nil];
                
                [self performSelector:@selector(cardLeftRemoveDismiss:) withObject:card afterDelay:0.1f];
                
                return;
            }
            
            //卡片还原到原先位置
            [self cardReCenterOrDismiss:NO card:card isRight:NO];
        }
    }
}

通过判断是向左还是向右滑动,当向左滑动到一定的距离后,移除该张卡片

/**
 0.25秒后移除重新创建卡片,移除该张卡片
 
 @param card card
 */
- (void)cardLeftRemoveDismiss:(INUserCardItemView *)card {
    [self cardReCenterOrDismiss:YES card:card isRight:NO];
}

/**
 0.25秒后移除重新创建卡片,移除该张卡片
 
 @param card card
 */
- (void)cardRightRemoveDismiss:(INUserCardItemView *)card {
    [self cardReCenterOrDismiss:YES card:card isRight:YES];
}

-(void)cardReCenterOrDismiss:(BOOL)isDismiss card:(INUserCardItemView *)card isRight:(BOOL)isRight{
    if (isDismiss) {
        //不显示关注或跳过icon
        // [card restoreCardIcon];
        
        [self cardRemove:card isRight:isRight];
    }  else {
        
        [card setScaleWithPercent:1 duration:0.25f completion:nil];
        [card setRorationWithAngle:0 duration:0.25f completion:nil];
        
        CGPoint targetCenter = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2);
        
        //不显示关注或跳过icon
        // [card restoreCardIcon];
        
        [card setCenter:targetCenter duration:0.35 completion:nil];
        [self cardRemove:nil isRight:isRight];
    }
}

/**
 移除卡片,
 
 @param card 卡片
 @param isRight 是否向右滑动
 */
-(void)cardRemove:(INUserCardItemView *)card isRight:(BOOL)isRight{
    if (card) {
        
        if (isRight) {
            //向右滑动,关注该用户
        }
        
        //不显示关注或跳过icon
        // [card restoreCardIcon];
        
        [card removeFromSuperview];
        [self.cardViews removeObjectIdenticalTo:card];
        
        
        //将卡片放到已经移除的数组中,方便撤回,限制五张图
        if (!isRight) {
            //如果跳过时候,才将移除的卡片放到数组中
        }
        
        if (self.cards.count <= 0) {
            
            for (UIView *subView in self.subviews) {
                if ([subView isKindOfClass:[INUserCardItemView class]]) {
                    [subView removeFromSuperview];
                }
            }
        }
        
        [self createNextCard];
    }
    
    [self updateCardStatus:NO];
}

移除之后需要创建一张新的卡片,放到剩余的卡片下面。

- (void)createNextCard {
    if (self.cardViews.count > 3) {
        return;
    }
    
    if (self.cards.count <= 0) {
        return;
    }
    
    id object = nil;
    if (self.cardViews.count > 0) {
        INUserCardItemView *lastCardView = [self.cardViews firstObject];
        if ([self.cards containsObject:lastCardView.data]) {
            NSInteger index = [self.cards indexOfObject:lastCardView.data];
            if (self.cards.count > (index+1)) {
                object = [self.cards objectAtIndex:(index+1)];
            }
        }
    } else {
        object = [self.cards objectAtIndex:0];
    }
    
    INUserCardItemView *cardView = [[INUserCardItemView alloc] initWithFrame:CGRectZero];
    cardView.frame = CGRectMake(kCardPadding, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds) - 2*kCardPadding,  CGRectGetWidth([UIScreen mainScreen].bounds) - 2*kCardPadding);

    cardView.hidden = YES;
    [cardView setScaleWithPercent:(self.cardViews.count-1)*kScalePrecent duration:0.25f completion:nil];
    
    CGPoint targetCenter = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2 + kCardPadding*(self.cardViews.count-1) + kCardOffsetY*(self.cardViews.count-1));
    
    [cardView setCenter:targetCenter duration:0.25f completion:nil];
    
    [self insertSubview:cardView belowSubview:[self.cardViews firstObject]];
    [self.cardViews insertObject:cardView atIndex:0];
}

那没有滑动到指定距离的时候,需要还原该张卡片。

- (void)updateCardStatus:(BOOL)isUpdated {
    for (int i = 0; i < self.cardViews.count; i++) {
        UIView *sView = self.cardViews[i];
        int alphaIndex = i;
        if (isUpdated) {
            if (i == self.cardViews.count-2) {
                alphaIndex = i+1;
            }
        }
        
        CGFloat theAplha = 1-((self.cardViews.count-1-alphaIndex)*kAlphaOffset);
        if (theAplha > 1.0) {
            theAplha = 1.0;
        }
        
        if (theAplha < 0.0) {
            theAplha = 0.0;
        }
        
        BOOL isBelowCard = NO;
        if ((i == 0) && (self.cardViews.count > 2)) {
            isBelowCard = YES;
        }
        
        if ([sView isKindOfClass:[INUserCardItemView class]]) {
            INUserCardItemView *cardView = self.cardViews[i];
            NSLog(@"card index:%d",i);
            
            cardView.alpha = 1.0;
            
            if (!isUpdated) {
                [cardView setScaleWithPercent:(1-kScalePrecent*(self.cardViews.count-1-i)) duration:0.25f completion:nil];
                
                CGPoint targetCenter = CGPointMake(CGRectGetWidth(self.bounds)/2, ceil(CGRectGetHeight(self.bounds)/2+kCardPadding*(self.cardViews.count-1-i) + kCardOffsetY*(self.cardViews.count-1-i)));
                [cardView setCenter:targetCenter duration:0.25f completion:nil];
            }
        }
    }
}

当滑动的过程中,需要用到动画效果。这里使用的POP来实现,用到的三个动画

#import "UIView+PopAnimation.h"
#import "POP.h"
#import 
#import 

@implementation UIView (PopAnimation)

- (void)setCenter:(CGPoint)center duration:(CGFloat)duration completion:(void (^) (BOOL finished))completion {
    POPBasicAnimation * bAni = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter];
    bAni.toValue = [NSValue valueWithCGPoint:center];
    bAni.duration = duration;
    [bAni setCompletionBlock:^(POPAnimation *ani, BOOL finished) {
        if (finished) {
            self.hidden = NO;
        }
        if (completion) {
            completion(finished);
        }
    }];
    [self pop_addAnimation:bAni forKey:@"center"];
}

- (void)setScaleWithPercent:(CGFloat)percent duration:(CGFloat)duration completion:(void (^) (BOOL finished))completion {
    POPBasicAnimation * bAni = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
    bAni.toValue = [NSValue valueWithCGSize:CGSizeMake(percent, percent)];
    bAni.duration = duration;
    [bAni setCompletionBlock:^(POPAnimation *ani, BOOL finished) {
        if (completion) {
            completion(finished);
        }
    }];
    [self.layer pop_addAnimation:bAni forKey:@"scale"];
}

- (void)setRorationWithAngle:(CGFloat)angele duration:(CGFloat)duration completion:(void (^) (BOOL finished))completion {
    POPBasicAnimation *bAni = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerRotation];
    bAni.duration = duration;
    bAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    bAni.toValue = [NSNumber numberWithFloat:angele];
    [bAni setCompletionBlock:^(POPAnimation *ani, BOOL finished) {
        if (completion) {
            completion(finished);
        }
    }];
    [self.layer pop_addAnimation:bAni forKey:@"rotation"];
}

@end

本文作为初学者的学习的记录,以便之后查阅。谢谢。

谢谢您的阅读,希望本站及文档能带给你帮助,给你带来简洁明了的阅读体验。谢谢。

实例demo:https://github.com/goodbruce/TanCardUI

你可能感兴趣的:(iphone开发,Objective-c,ios,cocoa,macos)