iOS开发:仿支付宝界面拖拽按钮动画

两种方式实现模仿支付宝生活界面可拖拽定制方块button的动画效果,当长按方块,可以拖拽方块到新的位置,其他的方块自动移动布局,也可以添加、删除方块。


预览



思路

  • 两种动画效果:一种是移动方块时与响铃方块交换位置,另一种是记录索引,方块到达新位置时其他方块依次迁移
  • 用到了ios框架的手势识别
  • 维持方块内部数据索引与界面布局一致
定义一个方块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);


}
当删除某个方块时,后面的方块也会自动补齐,有个动画效果。

源代码下载

csdn: 仿支付宝拖拽方块
github: DragTiles





你可能感兴趣的:(ios)