模仿探探的卡片流交互控件,項目地址:(github.com/chenzhengxu…)git
pod 'CardView'
CardView.h CardItemView.h
複製代碼
控件模仿了tableview的數據源和代理方法,以及tableviewcell的緩存池邏輯github
#import <UIKit/UIKit.h>
#import "CardItemView.h"
@class CardView;
@protocol CardViewDelegate <NSObject>
@optional
/** 點擊了第幾個itemView*/
- (void)cardView:(CardView *)cardView didClickItemAtIndex:(NSInteger)index;
@end
@protocol CardViewDataSource <NSObject>
@required
/** 一共有多少個CardItemView對象*/
- (NSInteger)numberOfItemViewsInCardView:(CardView *)cardView;
/** 返回第幾個CardItemView的對象*/
- (CardItemView *)cardView:(CardView *)cardView itemViewAtIndex:(NSInteger)index;
/** 要求請求更多數據*/
- (void)cardViewNeedMoreData:(CardView *)cardView;
@optional
- (CGSize)cardView:(CardView *)cardView sizeForItemViewAtIndex:(NSInteger)index;
@end
@interface CardView : UIView
/** 數據源*/
@property (nonatomic, weak) id <CardViewDataSource> dataSource;
/** 代理*/
@property (nonatomic, weak) id <CardViewDelegate> delegate;
/** 獲取標識符的CardItemView對象*/
- (CardItemView *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
/** 刪除第一個CardItemView對象 是否從左側*/
- (void)deleteTheTopItemViewWithLeft:(BOOL)left;
/** 重載視圖*/
- (void)reloadData;
@end
複製代碼
#import <UIKit/UIKit.h>
@class CardItemView;
@protocol CardItemViewDelegate <NSObject>
/** itemView從父視圖移除*/
- (void)cardItemViewDidRemoveFromSuperView:(CardItemView *)CardItemView;
/** item移動了多少角度,是否有動畫*/
- (void)cardItemViewDidMoveRate:(CGFloat)rate anmate:(BOOL)anmate;
@end
@interface CardItemView : UIView
/** 代理*/
@property (weak, nonatomic) id<CardItemViewDelegate> delegate;
/** 標識符*/
@property (nonatomic, readonly, copy) NSString *reuseIdentifier;
/** 初始化視圖*/
- (void)initView;
/** 初始化視圖,綁定標識符*/
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;
/** 從父視圖移除,是否從左側*/
- (void)removeWithLeft:(BOOL)left;
@end
複製代碼
繼承於CardItemView便可進行自定義的卡片流定製緩存
爲了優化內存,創建了緩存池,默認同時顯示4個CardItemView對象。bash
@property (copy, nonatomic) NSMutableDictionary *reuseDict;
複製代碼
同時最多隻有屏幕上可見的CardItemView對象存在內存中,可容許多個不一樣reuseIdentifier的CardItemView對象進行緩存調用,當第一個CardItemView對象從視圖上移除時,依然會保存在緩存池字典中,同時調用ide
- (CardItemView *)cardView:(CardView *)cardView itemViewAtIndex:(NSInteger)index;
複製代碼
去獲取最後個CardItemView對象,加入在視圖顯示中。優化
默認從最後提早5張調用datasource方法,相似於tableview的上拉加載動畫
- (void)cardViewNeedMoreData:(CardView *)cardView;
複製代碼
動畫原理,在CardItemView上添加了UIPanGestureRecognizer,根據拖拽手勢的action方法@selector(panGestHandle:)
反饋的拖拽狀況,來判斷CardItemView對象應該旋轉多少角度,是否應該從父視圖移除。ui
- (void)panGestHandle:(UIPanGestureRecognizer *)panGest {
if (panGest.state == UIGestureRecognizerStateChanged) {
CGPoint movePoint = [panGest translationInView:self];
_isLeft = (movePoint.x < 0);
self.center = CGPointMake(self.center.x + movePoint.x, self.center.y + movePoint.y);
CGFloat angle = (self.center.x - self.frame.size.width / 2.0) / self.frame.size.width / 4.0;
_currentAngle = angle;
self.transform = CGAffineTransformMakeRotation(-angle);
[panGest setTranslation:CGPointZero inView:self];
if ([self.delegate respondsToSelector:@selector(cardItemViewDidMoveRate:anmate:)]) {
CGFloat rate = fabs(angle)/0.15>1 ? 1 : fabs(angle)/0.15;
[self.delegate cardItemViewDidMoveRate:rate anmate:NO];
}
} else if (panGest.state == UIGestureRecognizerStateEnded) {
CGPoint vel = [panGest velocityInView:self];
if (vel.x > 800 || vel.x < - 800) {
[self remove];
return ;
}
if (self.frame.origin.x + self.frame.size.width > 150 && self.frame.origin.x < self.frame.size.width - 150) {
[UIView animateWithDuration:0.5 animations:^{
self.center = _originalCenter;
self.transform = CGAffineTransformMakeRotation(0);
if ([self.delegate respondsToSelector:@selector(cardItemViewDidMoveRate:anmate:)]) {
[self.delegate cardItemViewDidMoveRate:0 anmate:YES];
}
}];
} else {
[self remove];
}
}
}
複製代碼
根據CardItemView對象的下標index來調整每一個卡片不一樣的frame來體現卡片的層疊感。atom
CGSize size = [self itemViewSizeAtIndex:index];
[self insertSubview:itemView atIndex:0];
itemView.tag = index+1;
itemView.frame = CGRectMake(self.frame.size.width / 2.0 - size.width / 2.0, self.frame.size.height / 2.0 - size.height / 2.0, size.width, size.height);
複製代碼