iOS 卡片拖拽+翻转效果 。 仿tableview从缓存池中获取cell机制
1.效果图如下:
1.拖拽时分三种状态:
- (void)panOfRemoveMode:(UIPanGestureRecognizer *)gesture { if (gesture.state == UIGestureRecognizerStateBegan) { // 初始手指滑动的距离 __fingerPoiX = 0; // 当前项 __curItem = self.visiableItems[0]; // 上一项 __lastItem = __visibleIndex > 0 ? [self itemAtIndex:__visibleIndex - 1] : nil; if (__lastItem) { // 先添加上并设置初始位置 [__lastItem removeAlphaMaskView]; [self addSubview:__lastItem]; [self setFinalFrameForItem:__lastItem atIndex:-1 isUpdate:NO isLeftFinal:YES]; [self setTransformForItem:__lastItem atIndex:0]; } } else if (gesture.state == UIGestureRecognizerStateChanged) { CGPoint movedPoint = [gesture translationInView:self]; __fingerPoiX += movedPoint.x; // 避免一个手势左右滑动,即操作了 curItem,又操作了 lastItem if (__fingerPoiX < 0 && __lastItem) { [__lastItem removeFromSuperview]; __lastItem = nil; } // 左滑且未至底端 if (__fingerPoiX < 0 && __visibleIndex < [self numberOfItems] - 1) { [self calculateItemCenter:__curItem point:movedPoint isDeleteMode:NO]; [self adjustTranslateAngle:__curItem centerX:__curItem.center.x]; } // 右滑且未至顶端 else if (__fingerPoiX > 0 && __visibleIndex > 0 && __lastItem) { [self calculateItemCenter:__lastItem point:movedPoint isDeleteMode:NO]; [self adjustTranslateAngle:__lastItem centerX:__lastItem.center.x]; } [gesture setTranslation:CGPointZero inView:self]; } else if (gesture.state == UIGestureRecognizerStateEnded) { CGPoint vel = [gesture velocityInView:self]; if (__fingerPoiX < 0 && __visibleIndex < [self numberOfItems] - 1) { // 左滑 if (vel.x < -800) { [self cardItemOutOfScreenEndScrollAnimation:__curItem isLeftFinal:YES isFast:YES]; } else if(__fingerPoiX < -100){ [self cardItemOutOfScreenEndScrollAnimation:__curItem isLeftFinal:YES isFast:NO]; } else { [self cardItemToOriginalEndScrollAnimation:__curItem isFast:NO]; } } else if (__lastItem && __fingerPoiX > 0){ // 右滑 if(vel.x > 800) { [self cardItemToOriginalEndScrollAnimation:__lastItem isFast:YES]; } else if (__fingerPoiX > CARDITEM_RIGHT_RESPONDLENGTH){ [self cardItemToOriginalEndScrollAnimation:__lastItem isFast:NO]; } else { [self cardItemOutOfScreenEndScrollAnimation:__lastItem isLeftFinal:YES isFast:NO]; } } else { // 只对当前可视视图进行纠错 [self cardItemToOriginalEndScrollAnimation:__curItem isFast:NO]; } } }
/** * @brief 设置卡片项的放射变换 */ - (void)setTransformForItem:(CardViewItem *)item atIndex:(NSInteger)idx { CGAffineTransform scale = CGAffineTransformMakeScale(1 - self.scaleRatio * idx, 1); item.transform = CGAffineTransformTranslate(scale, 0, 15 * idx); }
/** * @brief 设置卡片项的最终位置约束 * @param idx 索引,用于新添加 item 还未设置 frame 时添加约束 * @param isUpdate 是否是更新约束 * @param isLeft yes - 最终位置在左侧 no - 最终位置在右侧 */ - (void)setFinalFrameForItem:(CardViewItem *)item atIndex:(NSInteger)idx isUpdate:(BOOL)isUpdate isLeftFinal:(BOOL)isLeft { // cx 代表 item.center.x; centerX 代表 item 与 self 中心点的距离 NSInteger cx = -300; NSInteger centerX = cx - self.center.x; // 如图:|← 300 →□← 300 →|( | 代表 item.center.x 位置, 300 代表距离,□ 代表self 视图) if (!isLeft) { cx = -cx + W(self); centerX = -centerX; } if (isUpdate) { // 设置旋转角度 [self adjustTranslateAngle:item centerX:cx]; item.center = CGPointMake(cx, H(self)/2 + 100); [item mas_updateConstraints:^(MASConstraintMaker * make) { make.centerX.equalTo(@(centerX)); make.centerY.equalTo(@(100)); }]; } else { CGRect rect = [self itemRectAtIndex:idx]; item.center = CGPointMake(cx, H(self)/2 + 100); [item mas_makeConstraints:^(MASConstraintMaker * make) { make.centerX.equalTo(@(centerX)); make.centerY.equalTo(@(100)); make.width.equalTo(@(rect.size.width)); make.height.equalTo(@(rect.size.height)); }]; } }
/** * @brief 设置卡片项的初始位置约束 * @attention item 缩放时其子视图也缩小;还原时,如果 item 与父视图没有约束,则 item 的子视图不会还原且在滑动时界面出错 */ - (void)setOriginalFrameForItem:(CardViewItem *)item atIndex:(NSInteger)idx isUpdate:(BOOL)isUpdate { SELF_WEAK; if (isUpdate) { // 约束不会导致 frame 调整 item.center = CGPointMake(W(self)/2, H(item)/2); item.transform = CGAffineTransformMakeRotation(0); [item mas_updateConstraints:^(MASConstraintMaker * make) { SELF_STRONG; make.centerX.equalTo(strongSelf); make.centerY.equalTo(@((H(item) - H(strongSelf))/2 )); }]; } else { CGRect rect = [self itemRectAtIndex:idx]; item.center = CGPointMake(W(self) / 2, rect.size.height / 2); [item mas_makeConstraints:^(MASConstraintMaker * make) { SELF_STRONG; make.centerX.equalTo(strongSelf); make.centerY.equalTo(@((rect.size.height - H(strongSelf)) /2 )); make.width.equalTo(@(rect.size.width)); make.height.equalTo(@(rect.size.height)); }]; } }
/** * @brief 从复用项数组中获取可复用对象,没有则新创建 。仿tableview的cell重用机制 */ - (CardViewItem *)dequeueReusableCellWithIdentifier:(NSString *)identifier { __block CardViewItem * item = nil; // 有可复用项 + 剩余的项 > _maxItems if (self.reusableItems.count > 0 && __visibleIndex <= [self numberOfItems] - self.maxItems) { [self.reusableItems enumerateObjectsUsingBlock:^(CardViewItem * obj, NSUInteger idx, BOOL * stop) { if ([obj.reuseIdentifier isEqualToString:identifier]) { item = obj; *stop = YES; } }]; } else { [self.mapDict enumerateKeysAndObjectsUsingBlock:^(NSString * key, id obj, BOOL * stop) { if ([key isEqualToString:identifier]) { if ([obj isKindOfClass:[NSString class]]) { // xib 文件 item = (CardViewItem *)[self viewFromXibFile:(NSString *)obj]; } else { // 类文件 item = [[(Class)obj alloc] init]; } item.reuseIdentifier = key; [item addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]]; *stop = YES; } }]; } return item; }