模仿iOS7 task switcher的卡片动画

原文地址:模仿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];
   }

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,755评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,059评论 4 62
  • 你转过头 看向远方 我寻着你的视线望去 我看见了你看见的 我不知道你是否看见了幸福 你依在桌角 簇拥着肩膀 眼底流...
    刘亚飞阅读 157评论 0 1
  • 由于前阵子室友看了《叶问3》这部电影,听到他说里面的有一段打斗场面还可以,于是处于对武侠的一点喜爱,昨晚也下载了这...
    yangwubolwg阅读 238评论 0 0
  • 我会做很多梦,然后,开始对每一项任务做可能性规划,能做的当即开始做,毫不犹豫。 不能做的或者没有条件做的,我会记下...
    饕餮思文阅读 376评论 2 1