两种方式实现模仿支付宝生活界面可拖拽定制方块button的动画效果,当长按方块,可以拖拽方块到新的位置,其他的方块自动移动布局,也可以添加、删除方块。
// // TileView.h // DragTiles // // Created by yxhe on 16/5/26. // Copyright © 2016年 yxhe. All rights reserved. // #import <UIKit/UIKit.h> @class TileButton; @protocol TileButtonDelegate<NSObject> @optional - (void)tileButtonClicked:(TileButton *)tileBtn; @end @interface TileButton : UIButton @property (nonatomic, assign) id<TileButtonDelegate> delegate; @property (nonatomic, assign) NSInteger index; //index in the tile array - (void)setTileText:(NSString *)text clickText:(NSString *)clickText; //set the tile text outside the class - (void)tileLongPressed; //tile longpressed and begin to move, called outside - (void)tileSuspended; //the tile touched pressed but not moved, called outside - (void)tileSettled; //cancel press or settle the tile to new place, called outside @end
// // TileView.m // DragTiles // // Created by yxhe on 16/5/26. // Copyright © 2016年 yxhe. All rights reserved. // #import "TileButton.h" @interface TileButton () @property (nonatomic, strong) UIButton *deleteButton; //the little del button @end @implementation TileButton - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if(self) { //set the main button style,in the tile button we can add many things self.backgroundColor = [UIColor yellowColor]; [self setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; // [self setTitleColor:[UIColor greenColor] forState:UIControlEventTouchDown]; //add the delete button _deleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; _deleteButton.frame = CGRectMake(self.bounds.size.width*6/7.0, 0, self.frame.size.width/7.0, self.frame.size.height/7.0); //use the relative coordinates _deleteButton.backgroundColor = [UIColor redColor]; _deleteButton.transform = CGAffineTransformMakeScale(0.1, 0.1); //set the deletebutton small at the beginning _deleteButton.hidden = YES; //hide it at the beginning [_deleteButton addTarget:self action:@selector(deleteButtonClicked) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_deleteButton]; } return self; } #pragma mark - called outside - (void)setTileText:(NSString *)text clickText:(NSString *)clickText { [self setTitle:text forState:UIControlStateNormal]; // [self setTitle:clickText forState:UIControlEventTouchDown]; } - (void)tileLongPressed { //make the tile half transparent and show the deletebutton [_deleteButton setHidden:NO]; //show the deletebutton [UIView animateWithDuration:0.3 animations:^{ self.alpha = 0.6; self.transform = CGAffineTransformMakeScale(1.1, 1.1); _deleteButton.transform = CGAffineTransformMakeScale(1.0, 1.0); }]; } - (void)tileSuspended { [_deleteButton setHidden:NO]; [UIView animateWithDuration:0.3 animations:^{ self.alpha = 0.6; self.transform = CGAffineTransformMakeScale(1.0, 1.0); _deleteButton.transform = CGAffineTransformMakeScale(1.0, 1.0); }]; } - (void)tileSettled { [UIView animateWithDuration:0.3 animations:^{ self.alpha = 1.0; self.transform = CGAffineTransformMakeScale(1.0, 1.0); _deleteButton.transform = CGAffineTransformMakeScale(0.1, 0.1); }]; [self performSelector:@selector(delayHide) withObject:nil afterDelay:0.3]; } - (void)delayHide { [_deleteButton setHidden:YES]; //the main button removed then the deletebutton automatically removed } #pragma button callback - (void)deleteButtonClicked { NSLog(@"delete button clicked"); if([self.delegate respondsToSelector:@selector(tileButtonClicked:)]) [self.delegate tileButtonClicked:self]; } @end在这个button类里面封装一些内容,比如处理手势长按和取消的函数,响应删除的委托事件回调(其实也可以用发送消息来做),还有button被长按后悬浮的放大透明动画,以及delete按钮缩放的动画。
- (void)onLongGresture:(UILongPressGestureRecognizer *)sender { #ifndef ALIPAY_ANIMATION [self handleFreeMove:sender]; #else [self handleSequenceMove:sender]; #endif }
//method 1: exchange the adjacent tiles, the sequence of the array elements will be disorderd - (void)handleFreeMove:(UILongPressGestureRecognizer *)sender { TileButton *tile_btn = sender.view; //get the dragged tilebutton switch(sender.state) { case UIGestureRecognizerStateBegan: startPos = [sender locationInView:sender.view]; originPos = tile_btn.center; [tile_btn tileSuspended]; touchState = SUSPEND; preTouchID = tile_btn.index; //save the ID of pretouched title break; case UIGestureRecognizerStateChanged: { [tile_btn tileLongPressed]; touchState = MOVE; //the tile will move CGPoint newPoint = [sender locationInView:sender.view]; CGFloat offsetX = newPoint.x - startPos.x; CGFloat offsetY = newPoint.y - startPos.y; tile_btn.center = CGPointMake(tile_btn.center.x + offsetX, tile_btn.center.y + offsetY); //get the intersect tile ID int intersectID = -1; for(NSInteger i = 0; i < _tileArray.count; i++) if(tile_btn != _tileArray[i] && CGRectContainsPoint([_tileArray[i] frame], tile_btn.center)) { intersectID = i; break; } if(intersectID != -1) { //swap every tile, the index remains unchanged __block TileButton *collisionButton = _tileArray[intersectID]; __block CGPoint tempOriginPos = collisionButton.center; //the new origin point [UIView animateWithDuration:0.3 animations:^{ collisionButton.center = originPos; //move the other title to the moved tile's origin pos originPos = tempOriginPos; //save the temp origin point in case the block shake }]; //exchange the tile index of the array [_tileArray exchangeObjectAtIndex:tile_btn.index withObjectAtIndex:intersectID]; //tile_btn still point to the moving tile, just swap the index int tempID = collisionButton.index; collisionButton.index = tile_btn.index; tile_btn.index = tempID; } } break; case UIGestureRecognizerStateEnded: { // [tile_btn tileSuspended]; [UIView animateWithDuration:0.3 animations:^{ tile_btn.center = originPos; }]; if(touchState == MOVE) //only if the pre state is MOVE, then settle, otherwise leave it suspend { touchState = UNTOUCHED; [tile_btn tileSettled]; //settle the tile to the new position(no need to use delay operation here) } } break; default: break; } }定义一个状态枚举,当长按手势处于不同的状态时进行状态转换,基本原理是,拖动方块过程中不断记录坐标位置,实现相邻方块交换位置。
//method 2: move the tiles inorder like Alipay, the order in array remains in sequence always - (void)handleSequenceMove:(UILongPressGestureRecognizer *)sender { TileButton *tile_btn = sender.view; //get the dragged tilebutton switch(sender.state) { case UIGestureRecognizerStateBegan: startPos = [sender locationInView:sender.view]; originPos = tile_btn.center; [tile_btn tileSuspended]; touchState = SUSPEND; preTouchID = tile_btn.index; //save the ID of pretouched title break; case UIGestureRecognizerStateChanged: { [tile_btn tileLongPressed]; touchState = MOVE; //the tile will move CGPoint newPoint = [sender locationInView:sender.view]; CGFloat offsetX = newPoint.x - startPos.x; CGFloat offsetY = newPoint.y - startPos.y; tile_btn.center = CGPointMake(tile_btn.center.x + offsetX, tile_btn.center.y + offsetY); //get the intersect tile ID int intersectID = -1; for(NSInteger i = 0; i < _tileArray.count; i++) if(tile_btn != _tileArray[i] && CGRectContainsPoint([_tileArray[i] frame], tile_btn.center)) { intersectID = i; break; } if(intersectID != -1) { if(abs(intersectID - tile_btn.index) == 1) //if the tiles are adjacent then move directly { __block TileButton *collisionButton = _tileArray[intersectID]; __block CGPoint tempOriginPos = collisionButton.center; //the new origin point [UIView animateWithDuration:0.3 animations:^{ collisionButton.center = originPos; //move the other title to the moved tile's origin pos originPos = tempOriginPos; //save the temp origin point in case the block shake }]; //exchange the tile index of the array [_tileArray exchangeObjectAtIndex:tile_btn.index withObjectAtIndex:intersectID]; //tile_btn still point to the moving tile, just swap the index int tempID = collisionButton.index; collisionButton.index = tile_btn.index; tile_btn.index = tempID; NSLog(@"tilebtn index:%d, intersect index:%d", [_tileArray[tile_btn.index] index], [_tileArray[collisionButton.index] index]); } else if(intersectID - tile_btn.index >1) //move the tiles to the left in order { CGPoint preCenter = originPos; CGPoint curCenter; //exchange the pointer in array and swap the index,at last the tile_btn is at the new right place for(int i = tile_btn.index + 1; i <= intersectID; i++) { __block TileButton *movedTileBtn = _tileArray[i]; curCenter = movedTileBtn.center; [UIView animateWithDuration:0.3 animations:^{ movedTileBtn.center = preCenter; }]; preCenter = curCenter; //save the precenter movedTileBtn.index--; //reduce the tile index _tileArray[i-1] = movedTileBtn; //move the pointer one by one } originPos = preCenter; tile_btn.index = intersectID; //exchange the ID _tileArray[intersectID] = tile_btn; //now make the last pointer point to the tile_btn NSLog(@"new tile btn index: %d", [_tileArray[tile_btn.index] index]); } else //move the tile to right in order { CGPoint preCenter = originPos; CGPoint curCenter; //exchange the pointer in array and swap the index,at last the tile_btn is at the new right place for(int i = tile_btn.index - 1; i >= intersectID; i--) { __block TileButton *movedTileBtn = _tileArray[i]; curCenter = movedTileBtn.center; [UIView animateWithDuration:0.3 animations:^{ movedTileBtn.center = preCenter; }]; preCenter = curCenter; //save the precenter movedTileBtn.index++; //reduce the tile index _tileArray[i+1] = movedTileBtn; //move the pointer one by one } originPos = preCenter; tile_btn.index = intersectID; //exchange the ID _tileArray[intersectID] = tile_btn; //now make the last pointer point to the tile_btn NSLog(@"new tile btn index: %d", [_tileArray[tile_btn.index] index]); } //test the display if the array is inorder for(TileButton *tile in _tileArray) NSLog(@"tile text: %@", tile.titleLabel.text); } } break; case UIGestureRecognizerStateEnded: { // [tile_btn tileSuspended]; [UIView animateWithDuration:0.3 animations:^{ tile_btn.center = originPos; }]; if(touchState == MOVE) //only if the pre state is MOVE, then settle, otherwise leave it suspend [tile_btn tileSettled]; //settle the tile to the new position(no need to use delay operation here) } break; default: break; } }这种动画的区别在于,方块移动到新的位置,别的方块自动依次按顺序补齐之前的空位置,基本原理是,保存方块索引,以及起始位置和终止位置,方块按顺序移动。
//tile delete button clicked - (void)tileButtonClicked:(TileButton *)tileBtn { //remove the button and adjust the tilearray NSLog(@"deletebutton delegate responds"); //remember the deleted tile's infomation int startIndex = tileBtn.index; CGPoint preCenter = tileBtn.center; CGPoint curCenter; //[_tileArray removeObject:tileBtn]; //delete the tile //exchange the pointer in array and swap the index,at last the tile_btn is at the new right place for(int i = startIndex + 1; i < _tileArray.count; i++) { __block TileButton *movedTileBtn = _tileArray[i]; curCenter = movedTileBtn.center; [UIView animateWithDuration:0.3 animations:^{ movedTileBtn.center = preCenter; }]; preCenter = curCenter; //save the precenter movedTileBtn.index--; //reduce the tile index _tileArray[i-1] = movedTileBtn; //move the pointer one by one } [_tileArray removeLastObject]; //every time remove the last object //must remove the tileBtn from the view [tileBtn removeFromSuperview]; //we can also use performselector so that button disappears with animation //test the display if the array is inorder for(TileButton *tile in _tileArray) NSLog(@"tile text: %@", tile.titleLabel.text); }当删除某个方块时,后面的方块也会自动补齐,有个动画效果。