iOS開発:アリペイインタフェースドラッグボタンアニメーション
15971 ワード
2つの方法は、支付宝の生活インタフェースを真似てカスタマイズされたブロックbuttonをドラッグ&ドロップするアニメーション効果を実現し、ブロックを長く押すと、ブロックを新しい位置にドラッグ&ドロップすることができ、他のブロックは自動的にレイアウトを移動したり、ブロックを追加したり、削除したりすることができます.
プレビュー
の2つのアニメーション効果:1つは、ブロックを移動するときにベルが鳴るブロックと位置を交換することであり、もう1つは記録インデックスであり、ブロックが新しい位置に達したときに他のブロックが に順次移動することである.はiosフレームワークのジェスチャー認識 を用いる.ブロック内部のデータインデックスがインタフェースレイアウトと一致することを維持する .
ブロックbuttonクラスを定義する
メインインタフェースでのジェスチャーの処理
アニメーション1
アニメーション2
アニメーションを削除
ソースのダウンロード
プレビュー
構想
ブロック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
}
アニメーション1
//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;
}
}
は、長押しジェスチャーが異なる状態にある場合に状態変換を行う状態列挙を定義し、基本原理は、ブロックをドラッグする過程で座標位置を絶えず記録し、隣接するブロック交換位置を実現することである.アニメーション2
//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);
}
あるブロックを削除すると、後ろのブロックも自動的に補完され、アニメーション効果があります.