在UIcollectionView中实现相似电脑资源管理器里的那种将文件拖入图标就能够完成添加操做的效果,如图:git
这个gif通过压缩,效果不太好。实际效果比图上顺滑不少。github
首先我下载了 https://github.com/wazrx/XWDr...
这个控件做为基础,感谢做者!ide
XWDragCellCollectionView能够实现垂直/水平的滚动以及滑动排序,这个不是研究的重点,我就不重复造轮子,而是在它的基础上来改出咱们想要的功能。动画
首先添加手势,用长按手势激活cell,来进行接下来的操做:spa
- (void)initUI { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) collectionViewLayout:flowLayout]; self.collectionView = collectionView; [self.view addSubview:collectionView]; collectionView.delegate = self; collectionView.dataSource = self; [collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:identity]; collectionView.alwaysBounceVertical = YES; UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongGesture:)]; [collectionView addGestureRecognizer:longGesture]; }
这是长按手势所要激活的方法:code
- (void)handleLongGesture:(UILongPressGestureRecognizer *)longGesture { switch (longGesture.state) { case UIGestureRecognizerStateBegan: [self gestureBegan:longGesture]; break; case UIGestureRecognizerStateChanged: [self gestureChange:longGesture]; break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: [self gestureEndOrCancle:longGesture]; break; default: break; } }
方法中分别对操做的开始、拖动和结束/取消作了处理,先看开始的方法:orm
- (void)gestureBegan:(UILongPressGestureRecognizer *)longPressGesture { self.originalIndexPath = [self.collectionView indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:self.originalIndexPath];//拿到被长按的格子 //下面这一片是对这个格子截图 UIImage *snap; UIGraphicsBeginImageContextWithOptions(cell.bounds.size, 1.0f, 0); [cell.layer renderInContext:UIGraphicsGetCurrentContext()]; snap = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //把截好的图片装进一个假的View里,以后用这个View随手指拖动而运动而隐藏原格子,给用户形成实际上是格子被拖动的效果 UIView *tempMoveCell = [UIView new]; tempMoveCell.layer.contents = (__bridge id)snap.CGImage; cell.hidden = YES; self.orignalCell = cell; self.orignalCenter = cell.center; self.tempMoveCell = tempMoveCell; self.tempMoveCell.frame = cell.frame; [self.collectionView addSubview:self.tempMoveCell]; //开启边缘滚动定时器 [self setEdgeTimer]; //开启抖动,和原控件不一样,我这个是只抖两下 [self itemshake]; self.lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view]; } - (void)itemshake { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; [animation setDuration:0.1]; animation.fromValue = @(-M_1_PI/6); animation.toValue = @(M_1_PI/6); animation.repeatCount = 1; animation.autoreverses = YES; self.tempMoveCell.layer.anchorPoint = CGPointMake(0.5, 0.5); [self.tempMoveCell.layer addAnimation:animation forKey:@"rotation"]; }
接下来是重点的拖动时候调用的方法:blog
- (void)gestureChange:(UILongPressGestureRecognizer *)longPressGesture { CGFloat tranX = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].x - self.lastPoint.x; CGFloat tranY = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].y - self.lastPoint.y; self.tempMoveCell.center = CGPointApplyAffineTransform(self.tempMoveCell.center, CGAffineTransformMakeTranslation(tranX, tranY)); //让你的假格子View随手指移动 self.lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view]; [self handleCell]; }
handleCell方法则负责分辨具体的操做排序
#define MoveSpace 20 //在格子里的这么大范围内也算move操做的触发点,而不是add操做的触发点 - (void)handleCell { for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {//遍历全部的可视cell if ([self.collectionView indexPathForCell:cell] == _originalIndexPath) {//若是是本身这个cell,那么跳过 continue; } //计算全部表格中心和在移动的格子中心的距离 CGFloat spacingX = fabs(self.tempMoveCell.center.x - cell.center.x); CGFloat spacingY = fabs(_tempMoveCell.center.y - cell.center.y); if (self.motherCell == cell) { if (spacingX > _tempMoveCell.bounds.size.width / 2.0f - MoveSpace || spacingY > _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) { //跑出了格子 [self stopAddToCellWithData:NO]; } } if (spacingX <= _tempMoveCell.bounds.size.width / 2.0f - MoveSpace && spacingY <= _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) { // NSLog(@"进入格子内"); self.motherCell = cell; [self setAddTimer]; self.moveIndexPath = [self.collectionView indexPathForCell:cell]; } else if (spacingX >= _tempMoveCell.bounds.size.width / 2.0f - MoveSpace && spacingX <= _tempMoveCell.bounds.size.width / 2.0f + 7.5 && spacingY <= _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) { //移动 self.willMoveCell = cell; [self setMoveTimer]; break; } } }
看代码到这里,你可能会发现setMoveTimer、setAddTimer 和 setEdgeTimer三个方法我并无解释,这三个是计时器,用来延迟用户操做,提高手感。好比setMoveTimer图片
- (void)setMoveTimer { if (!_moveTimer) { [self stopAddTimer]; // NSLog(@"新建timer"); _moveTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(moveCell) userInfo:nil repeats:NO]; } } - (void)stopMoveTimer { if (_moveTimer) { // NSLog(@"销毁timer"); [_moveTimer invalidate]; _moveTimer = nil; } }
他规定了用户将格子移动到会触发move(排序)操做的位置时有0.5秒钟的缓冲而setAddTimer:
- (void)setAddTimer { if (!_addTimer) { [self stopMoveTimer]; // NSLog(@"新建timer"); _addTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addToCell) userInfo:nil repeats:NO]; } } - (void)stopAddTimer { if (_addTimer) { // NSLog(@"销毁timer"); [_addTimer invalidate]; _addTimer = nil; } }
则让add操做有1秒的缓冲,同时这两个计时器在生成时都会ban掉对方,防止操做混乱。
setEdgeTimer是负责拖动按钮移动到屏幕边缘触发滚动的。这个是原控件里就有的方法,不做讲解。
在移动计时器到时间后,就会触发move操做,此逻辑主要来自原控件
- (void)moveCell { NSLog(@"%@", [self.collectionView cellForItemAtIndexPath:self.originalIndexPath]); self.moveIndexPath = [self.collectionView indexPathForCell:self.willMoveCell]; self.orignalCell = self.willMoveCell; self.orignalCenter = self.willMoveCell.center; [CATransaction begin]; [self.collectionView moveItemAtIndexPath:self.originalIndexPath toIndexPath:self.moveIndexPath]; [CATransaction setCompletionBlock:^{ // NSLog(@"动画完成"); [self stopMoveTimer]; }]; [CATransaction commit]; self.originalIndexPath = self.moveIndexPath; }
而add操做则分两步:开始add和结束add。结束add又分两种状况:完成add操做和取消add操做。完成add操做要对文件进行更改,取消add操做要将表格还原到操做以前的状态。首先看开始add:
- (void)addToCell { // NSLog(@"开始add操做"); if (self.motherCell) { // NSLog(@"开始放大"); //跟上面同样,对添加操做做为文件夹一方的格子(motherCell)进行截图 UIImage *snap; UIGraphicsBeginImageContextWithOptions(self.motherCell.bounds.size, 1.0f, 0); [self.motherCell.layer renderInContext:UIGraphicsGetCurrentContext()]; snap = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //把截图装进一个假的View里 UIView *bigMotherCell = [UIView new]; bigMotherCell.layer.contents = (__bridge id)snap.CGImage; // self.motherCell.hidden = YES; self.bigMotherCell = bigMotherCell; self.bigMotherCell.frame = self.motherCell.frame; [self.collectionView addSubview:self.bigMotherCell]; CGRect rect = self.bigMotherCell.frame; CGFloat scale = 1.3; [self.collectionView bringSubviewToFront:self.tempMoveCell]; //方法这个假View,形成文件夹要容纳文件的效果 [UIView animateWithDuration:0.5 animations:^{ self.bigMotherCell.frame = CGRectMake(rect.origin.x - rect.size.width * (scale - 1)/2, rect.origin.y - rect.size.height * (scale - 1)/2, rect.size.width * scale, rect.size.height * scale); } completion:nil]; } else { // NSLog(@"没有motherCell"); } }
结束add的两种状况我选择用一个bool值进行区分,yes是完成操做,no是取消操做
- (void)stopAddToCellWithData:(BOOL)withData { if (self.motherCell) { [UIView animateWithDuration:0.1 animations:^{ self.bigMotherCell.frame = self.motherCell.frame; if (withData) { // NSIndexPath *motherIndexPath = [self.collectionView indexPathForCell:self.motherCell]; NSIndexPath *childIndexPath = [self.collectionView indexPathForCell:self.orignalCell]; NSLog(@"把编号为%@的格子移动到编号为%@的格子里",((CollectionViewCell *)self.orignalCell).number, ((CollectionViewCell *)self.motherCell).number); [self.dataArray removeObjectAtIndex:childIndexPath.row]; [self.collectionView deleteItemsAtIndexPaths:@[childIndexPath]]; } } completion:^(BOOL finished) { [self.bigMotherCell removeFromSuperview]; self.motherCell = nil; [self stopAddTimer]; }]; } }
最后就是结束长按的方法,他主要负责让一切回归初始
- (void)gestureEndOrCancle:(UILongPressGestureRecognizer *)longPressGesture { self.collectionView.userInteractionEnabled = NO; [self stopEdgeTimer]; if (self.motherCell) { [self stopAddToCellWithData:YES]; [self removeTempMoveCell]; } else { [UIView animateWithDuration:0.25 animations:^{ self.tempMoveCell.center = self.orignalCenter; } completion:^(BOOL finished) { [self removeTempMoveCell]; }]; } }
这就是拖入-添加操做的全部逻辑,若是还不明白能够来这里下载Demo试试:
https://github.com/zyf25333/d...