在陌陌探探等交友軟件中,有個(gè)很經(jīng)典的左滑喜歡,右滑下一個(gè)的效果。還有個(gè)撤銷上一次不喜歡的功能。VIP可以無限撤銷。工作有這個(gè)需求,就搜了下,明白了思路,就自己動(dòng)手敲一遍,理清思路,修改成適合自己使用的。記錄一下過程,省的時(shí)間長(zhǎng)了就忘了。
先上效果圖:

核心:拖動(dòng)手勢(shì)的處理,在拖動(dòng)過程中改變各個(gè)view位置和大小。主要是計(jì)算center.y的改變。
代碼實(shí)現(xiàn)過程中,各個(gè)圖片的間距、縮放大小、圖片大小需要經(jīng)常使用,所以設(shè)置了宏,方便使用和修改。
并使用一個(gè)全局變量數(shù)組,存放4個(gè)圖片對(duì)象,方便處理數(shù)據(jù)。
設(shè)置兩個(gè)全局變量分別記錄最上層圖片和最下層圖片。
@property (nonatomic, strong) NSMutableArray *cards;
@property (nonatomic, strong) UIView *topCard; //最上面
@property (nonatomic, strong) UIView *bottomCard; //最底部
#define ImageWidth 200
#define ImageHeight 300
#define ImageScale 0.1 //每張圖片初始化縮小尺寸
#define ImageSpace 20 //每張圖片底部距離
過程分析:
一、初始化。
可以看到默認(rèn)顯示3張圖片。拖動(dòng)第一張過程中,其他2張往上移動(dòng),移動(dòng)過程中逐步變大。最后一張沒變化。所以總共需要初始化4個(gè)圖片。
由于默認(rèn)顯示3張,第3張和第4張的大小位置相同。
每張圖片初始化后,就加載顯示,并調(diào)用sendSubviewToBack方法,把新建的對(duì)象放到最下層,后面的圖片根據(jù)for循環(huán)里初始化代碼,位置越來越靠下,并且越來越小。初始化完成。
for (int i = 0; i < 4; i++) {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ImageWidth, ImageHeight)];
card.tag = 100 + i;
int index = i;
if (index == 3) {
index = 2;
}
// y坐標(biāo) = 屏幕中心點(diǎn)+中心點(diǎn)下移的距離+每個(gè)圖片距離第一張圖片的間距
// 圖片高度*縮放比例 = 圖片減小的總大小。除以2是中心下移的距離,就可以和第一張圖片底部對(duì)齊
card.center = CGPointMake(ScreenW/2, ScreenH/2 +(ImageHeight*ImageScale*index/2)+ ImageSpace*index);
card.transform = CGAffineTransformMakeScale(1-ImageScale*index, 1-ImageScale*index);
card.backgroundColor = self.dataArr[i];
[self.cards addObject:card];
[self.view addSubview:card];
[self.view sendSubviewToBack:card];
//添加拖動(dòng)手勢(shì)
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panHandle:)];
[card addGestureRecognizer:pan];
card.userInteractionEnabled = NO;
if (i == 0) {
card.userInteractionEnabled = YES;
self.topCard = card;
}else if (i == 3){
self.bottomCard = card;
}
}
二、拖動(dòng)過程中
先處理拖動(dòng)的圖片。由于第一張沒有縮放,拖動(dòng)過程中也不需要改變大小,所以只考慮位置,改變中心點(diǎn)即可。
根據(jù)translationInView:方法得到手指拖動(dòng)移動(dòng)的距離,改變center就行了。調(diào)用setTranslation:CGPointZero重置拖動(dòng)距離,每次計(jì)算完畢改完center就從新開始。
其中,往左往后滑動(dòng),最多也就拖動(dòng)半個(gè)屏幕的距離,所以根據(jù)屏幕的一半計(jì)算出拖動(dòng)距離在半個(gè)屏幕中的比例XOffPercent。從0到1。是0就說明沒移動(dòng),其他的圖片不要改變,是1的說明拖動(dòng)結(jié)束,當(dāng)前圖片移出屏幕,第2張大小和位置變成第一張的大小和位置,其他圖片依次類推。
CGPoint transLcation = [pan translationInView:cardView];
cardView.center = CGPointMake(cardView.center.x + transLcation.x, cardView.center.y + transLcation.y);
CGFloat XOffPercent = (cardView.center.x-ScreenW/2.0)/(ScreenW/2.0);
CGFloat rotation = M_PI_2/4*XOffPercent;
cardView.transform = CGAffineTransformMakeRotation(rotation);
[pan setTranslation:CGPointZero inView:cardView];
根據(jù)滑動(dòng)比例XOffPercent,處理其他圖片的大小和位置。
拖動(dòng)過程中調(diào)用。
一個(gè)for循環(huán),除去第一張和最后一張,改變中間兩張的大小。主要是center的y值比較麻煩。同時(shí)更改圖片的選擇角度,看起來美觀一些。
這個(gè)高度計(jì)算是核心,這個(gè)搞明白了,其他的都不是問題了。
主要就是原始位置減去根據(jù)滑動(dòng)比例引起的位置改變。
其中XOffPercent*ImageSpace代表每張圖片只需要移動(dòng)一個(gè)間隔的距離。
(ImageHeight*ImageScale*index/2)*XOffPercent/index,中心點(diǎn)需要移動(dòng)的距離,最后除以index,代表只需要移動(dòng)到相鄰中心點(diǎn)的距離。
剛做的時(shí)候,沒除index,效果不對(duì),想了好大一會(huì)才明白問題出哪了。
- (void)animationBlowViewWithXOffPercent:(CGFloat)XOffPercent {
for (UIView *card in self.cards) {
if (card != self.topCard && card.tag != 103) {
NSInteger index = card.tag-100;
card.center = CGPointMake(ScreenW/2,ScreenH/2
+ (ImageHeight*ImageScale*index/2)
+ ImageSpace*index //上面3行是原始位置,下面2行是改變的大小
- XOffPercent*ImageSpace
- (ImageHeight*ImageScale*index/2)*XOffPercent/index);
CGFloat scale = 1-ImageScale*index + XOffPercent*ImageScale;
card.transform = CGAffineTransformMakeScale(scale, scale);
}
}
}
三、拖動(dòng)結(jié)束
根據(jù)距離屏幕的距離自行判斷是否需要移除圖片。
如果不需要移除,就用UIView動(dòng)畫把第一張圖片恢復(fù)成原始狀態(tài),并調(diào)用animationBlowViewWithXOffPercent方法處理其他的圖片,滑動(dòng)比例是0。表示其他圖片也恢復(fù)成原始狀態(tài)。
如果需要移除,滑動(dòng)比例是1。最后更改第一張圖片和最后一張圖片的值,重置每張圖片的tag值等,沒什么好說的
-(void)cardRemove{
// 滑動(dòng)結(jié)束第一張和放到數(shù)組最后
[self.cards removeObject:self.topCard];
[self.cards addObject:self.topCard];
// 重新設(shè)置tag
for (int i = 0 ; i < 4; i++) {
UIView *card = self.cards[i];
card.tag = 100+i;
}
// 重置第一張和最后一張(第4)
self.topCard.userInteractionEnabled = NO;
self.topCard.center = CGPointMake(ScreenW/2, ScreenH/2 + (ImageHeight*ImageScale*2/2) + ImageSpace*2);
self.topCard.transform = CGAffineTransformMakeScale(1-ImageScale*2, 1-ImageScale*2);
[self.view sendSubviewToBack:self.topCard];
self.bottomCard = self.topCard;
UIView *currentCard = self.cards.firstObject;
currentCard.userInteractionEnabled = YES;
self.topCard = currentCard;
}
四、點(diǎn)擊按鈕滑動(dòng)
和拖動(dòng)類似,就是UIView動(dòng)畫,改變第一張的位置,同時(shí)更改其他圖片的大小和位置,原理一樣。最后重置tag,更改第一張和最后一張全局變量的值.
計(jì)算y的值還是關(guān)鍵。
[UIView animateWithDuration:0.5 animations:^{
self.topCard.center = CGPointMake(-ImageWidth, ScreenH/2+50);
self.topCard.transform = CGAffineTransformMakeRotation(-M_PI_4/2);
for (UIView *card in self.cards) {
if (card.tag != 100 && card.tag != 103) {
NSInteger index = card.tag - 100;
card.center = CGPointMake(ScreenW/2, ScreenH/2
+ ImageHeight*index*ImageScale/2
+ ImageSpace*index //原始位置
- ImageSpace
- (ImageHeight*ImageScale*index/2)/index);
CGFloat scale = 1-index*ImageScale + ImageScale;
card.transform = CGAffineTransformMakeScale(scale, scale);
}
}
}completion:^(BOOL finished) {
sender.userInteractionEnabled = YES;
[self cardRemove];
}];
移除第一張,變成最后一張。更改數(shù)組里對(duì)象的位置和tag。
-(void)cardRemove{
// 滑動(dòng)結(jié)束第一張和放到數(shù)組最后
[self.cards removeObject:self.topCard];
[self.cards addObject:self.topCard];
// 重新設(shè)置tag
for (int i = 0 ; i < 4; i++) {
UIView *card = self.cards[i];
card.tag = 100+i;
}
// 重置第一張和最后一張(第4)
self.topCard.userInteractionEnabled = NO;
self.topCard.center = CGPointMake(ScreenW/2, ScreenH/2 + (ImageHeight*ImageScale*2/2) + ImageSpace*2);
self.topCard.transform = CGAffineTransformMakeScale(1-ImageScale*2, 1-ImageScale*2);
[self.view sendSubviewToBack:self.topCard];
self.bottomCard = self.topCard;
UIView *currentCard = self.cards.firstObject;
currentCard.userInteractionEnabled = YES;
self.topCard = currentCard;
}
五、撤銷返回
就是把最后一張變成第一張,第一張變成第二張。其他的圖片中心往上移動(dòng),同時(shí)變大。
最后重置tag,更改第一張和最后一張全局變量的值。
過程參考第四部,實(shí)現(xiàn)過程類似,就不貼了。