• 可拖拽重排的CollectionView


    来源:wazrx

    链接:http://www.jianshu.com/p/8f0153ce17f9

    写在前面

    这段时间都在忙新项目的事儿,没有时间倒腾,这两天闲下来,想着一直没有细细的研究CollectionView,一般最多用来做点循环滚动,所以花时间深入学习了一些东西,这次实现了CollectionView的拖动重排的效果,先请看图:(吐槽:不知道为啥从xcode7开始,模拟器变得很卡很卡,所以截图的效果不好,大家可以在真机上测试,效果还是非常不错的)

    2月27日更新:

    修复了拖拽滚动时抖动的一个bug,新增编辑模式,进入编辑模式后不用长按触发手势,且在开启抖动的情况下会自动进入抖动模式,如图:

    配合瀑布流(我直接使用了上个项目的瀑布流模块做了集成实验)

    我将整个控件进行了封装,名字是XWDragCellCollectionView使用起来非常方便,github地址:https://github.com/wazrx/XWDragCellCollectionView;使用也非常简单,只需3步,步骤如下:

    1、继承于XWDragCellCollectionView;

    2、实现必须实现的DataSouce代理方法:(在该方法中返回整个CollectionView的数据数组用于重排)

        - (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView;

    3、实现必须实现的一个Delegate代理方法:(在该方法中将重拍好的新数据源设为当前数据源)(例如 :_data = newDataArray)

        - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray;

    详细的使用可以查看代码中的demo,支持设置长按事件,是否开启边缘滑动,抖动、以及设置抖动等级,这些在h文件里面都有详细说明,有需要的可以尝试一下,并多多提意见,作为新手,肯定还有很多不足的地方;

    原理

    在刚刚考虑这个效果的时候,我仔细分析了一下效果,我首先想到的就是利用截图大法,将手指要移动的cell截个图来进行移动,并隐藏该cell,然后在合适的时候交换cell的位置,造成是拖拽cell被拖拽到新位置的效果,我将主要实现的步骤分为如下步骤:

    1、给CollectionView添加一个长按手势,用于效果驱动

    - (void)xwp_addGesture{

        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(xwp_longPressed:)];

        _longPressGesture = longPress;

        //设置长按时间

        longPress.minimumPressDuration = _minimumPressDuration;

        [self addGestureRecognizer:longPress];

    }

    2、在手势开始的时候,得到手指所在的cell,并截图,并将原有cell隐藏

    - (void)xwp_gestureBegan:(UILongPressGestureRecognizer *)longPressGesture{

        //获取手指所在的cell

        _originalIndexPath = [self indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]];

        UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath];

        //截图大法,得到cell的截图视图

        UIView *tempMoveCell = [cell snapshotViewAfterScreenUpdates:NO];

        _tempMoveCell = tempMoveCell;

        _tempMoveCell.frame = cell.frame;

        [self addSubview:_tempMoveCell];

        //隐藏cell

        cell.hidden = YES;

        //记录当前手指位置

        _lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];

    }

    3、在手势移动的时候,计算出手势移动的距离,并移动截图视图,当截图视图于某一个cell(可见cell)相交到一定程度的时候,我就让调用系统的api交换这个cell和隐藏cell的位置,形成动画,同时更新数据源(更新数据源是最重要的操作!)

    - (void)xwp_gestureChange:(UILongPressGestureRecognizer *)longPressGesture{

        //计算移动距离

        CGFloat tranX = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].x - _lastPoint.x;

        CGFloat tranY = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].y - _lastPoint.y;

        //设置截图视图位置

        _tempMoveCell.center = CGPointApplyAffineTransform(_tempMoveCell.center, CGAffineTransformMakeTranslation(tranX, tranY));

        _lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];

        //计算截图视图和哪个cell相交

        for (UICollectionViewCell *cell in [self visibleCells]) {

            //剔除隐藏的cell

            if ([self indexPathForCell:cell] == _originalIndexPath) {

                continue;

            }

            //计算中心距

            CGFloat space = sqrtf(pow(_tempMoveCell.center.x - cell.center.x, 2) + powf(_tempMoveCell.center.y - cell.center.y, 2));

            //如果相交一半就移动

            if (space

    /**

    *  更新数据源

    */

    - (void)xwp_updateDataSource{

        NSMutableArray *temp = @[].mutableCopy;

        //通过代理获取数据源,该代理方法必须实现

        if ([self.dataSource respondsToSelector:@selector(dataSourceArrayOfCollectionView:)]) {

            [temp addObjectsFromArray:[self.dataSource dataSourceArrayOfCollectionView:self]];

        }

        //判断数据源是单个数组还是数组套数组的多section形式,YES表示数组套数组

        BOOL dataTypeCheck = ([self numberOfSections] != 1 || ([self numberOfSections] == 1 && [temp[0] isKindOfClass:[NSArray class]]));

        //先将数据源的数组都变为可变数据方便操作

        if (dataTypeCheck) {

            for (int i = 0; i  _originalIndexPath.item) {

                for (NSUInteger i = _originalIndexPath.item; i  _moveIndexPath.item ; i --) {

                    [orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i - 1];

                }

            }

        }else{

        //在不同section之间移动的情况(原理是删除原位置所在section的cell并插入到新位置所在的section中)

            NSMutableArray *orignalSection = temp[_originalIndexPath.section];

            NSMutableArray *currentSection = temp[_moveIndexPath.section];

            [currentSection insertObject:orignalSection[_originalIndexPath.item] atIndex:_moveIndexPath.item];

            [orignalSection removeObject:orignalSection[_originalIndexPath.item]];

        }

        //将重排好的数据传递给外部,在外部设置新的数据源,该代理方法必须实现

        if ([self.delegate respondsToSelector:@selector(dragCellCollectionView:newDataArrayAfterMove:)]) {

            [self.delegate dragCellCollectionView:self newDataArrayAfterMove:temp.copy];

        }

    }

    4、手势结束的时候将截图视图动画移动到隐藏cell所在位置,并显示隐藏cell并移除截图视图;

    - (void)xwp_gestureEndOrCancle:(UILongPressGestureRecognizer *)longPressGesture{

        UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath];

        //结束动画过程中停止交互,防止出问题

        self.userInteractionEnabled = NO;

        //给截图视图一个动画移动到隐藏cell的新位置

        [UIView animateWithDuration:0.25 animations:^{

            _tempMoveCell.center = cell.center;

        } completion:^(BOOL finished) {

            //移除截图视图、显示隐藏cell并开启交互

            [_tempMoveCell removeFromSuperview];

            cell.hidden = NO;

            self.userInteractionEnabled = YES;

        }];

    }

    关键效果的代码就是上面这些了,还有写细节的东西请大家自行查看源代码

    写在最后

    从iOS9开始,系统已经提供了重排的API,不用我们这么辛苦的自己写,不过想要只适配iOS9,还有一段时间,不过大家可以尝试去实现以下这几个API:

    // Support for reordering

    - (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0); // returns NO if reordering was prevented from beginning - otherwise YES

    - (void)updateInteractiveMovementTargetPosition:(CGPoint)targetPosition NS_AVAILABLE_IOS(9_0);

    - (void)endInteractiveMovement NS_AVAILABLE_IOS(9_0);

    - (void)cancelInteractiveMovement NS_AVAILABLE_IOS(9_0);

    接下来,还准备研究一下CollectionView的转场和自定义布局,已经写了一些自定义布局效果了,总结好了再贴出来,CollectionView实在是一枚非常强大的控件,大家都应该去深入的研究一下,说不定会产生许多奇妙的想法!加油咯!最后复习一下github地址:https://github.com/wazrx/XWDragCellCollectionView,如果觉得有帮助,请给与一颗star鼓励一下,谢谢!

  • 相关阅读:
    web测试方法总结
    APP测试点总结
    函数初识
    字符编码及文件操作
    简单购物车程序(Python)
    基本数据类型(列表,元祖,字典,集合)
    python基础
    基本数据类型(数字和字符串)
    Python入门
    操作系统
  • 原文地址:https://www.cnblogs.com/fengmin/p/5679973.html
Copyright © 2020-2023  润新知