Swipe-to-Select照片滑动选择实现

前言


我们的产品突然提出一个需求,希望让用户更快地选择照片,通过滑动的方式而不是一张一张点击选择,并且给了我们一个参考对象,iPhone手机相册。

一开始准备从UITouch和响应链入手,然后根据坐标各种计算。实际操作后发现工程量太大,不好实现。后来打算用UISwipeGesture,但是上下左右控制十分麻烦,不得不放弃。

最后github上找到一个类似的demo:Swipe to Select GridView,总算为该事件的实现打开思路。不过,原demo和我们的项目实际需求相差甚远,于是自己动手实现了该效果。我们先来看看效果:

2017-04-08 16_11_12.gif

demo


demo地址:https://pan.baidu.com/s/1nvBcN8l

核心思想


UIPanGestureRecognizer

其实一开始看原项目中是用UIPanGestureRecognizer手势来实现滑动定位的时候还是很吃惊的,一直以为UIPanGestureRecognizer是用来做缩放之类的手势,没想到滑动手势也能胜任。更神奇的是,如果添加到view上,而view存在UICollection,纵向滑动优先触发scrollView的上下滑动,横向滑动就触发PanGesture事件后又能纵向滑动了,不需要自己写代码控制,简直和iPhone相册一模一样。(后来根据响应链的思路想想也应该是这样。。collectionView在View的前面嘛。。)

gestureRecognizer 只要设置了最大和最小触点都是1就能识别单点滑动事件。只要响应了该手势,就能拿到UIPanGestureRecognizer对象,通过 [gestureRecognizer locationInView:collectionView]方法就能获得当前触点在collectionView中的位置,然后进一步比较,判断选择不选择。

  • UIGestureRecognizerStateBegan
  • UIGestureRecognizerStateEnded

UIPanGestureRecognizer有一个state属性,当手指触发事件的时候,state == UIGestureRecognizerStateBegan,这时就能进行一些手势开始的操作,比如标记进入滑动状态等。当手指离开屏幕的时候,state == UIGestureRecognizerStateEnded,这时进行手势结束操作等。其他时刻可以根据点的位置进行判断cell选中不选中。

选中 & 不选中

仔细分析iPhone相册cell选中不选中的实现可以发现规律:

  • 找到第一个进入区域的cell和最后一个进入区域的cell,然后将2个cell位置之间的cell改变状态

如图,我只选中红色区域,蓝色区域也跟着选中。

Swipe-to-Select照片滑动选择实现_第1张图片
区域选中
  • 改变的值是第一个cell变化的值

如果第一个cell变成选中,那么后面变化的cell全都是选中。如果第一个cell变成不选中,那么后面变化的cell全都不选中。

  • cell先进入选中区域,然后离开选中区域,那么选中与否与cell进入选中区域之前保持一致

这一点就比较复杂了,也就是说手指滑动进入状态后,需要产生一个临时值来保存当前选中状态(tmpIsSelected)而不是最终选中状态(isSelected)。这时候就需要结合UIGestureRecognizerStateBeganUIGestureRecognizerStateEnded进行判断。

大致思路如下(参考demo):

  • 1、进入滑动状态,将所有model的isSelected的值赋值给tmpIsSelected,界面打钩不打钩的依据完全按照tmpIsSelected属性的值来显示。

  • 2、在滑动状态中(手指滑动),cell状态改变都修改model的tmpIsSelected值。

  • 3、结束滑动状态(手指离开),将model的tmpIsSelected的值赋值给isSelected,是否打钩都依据isSelected值显示。

区域判断

Swipe-to-Select照片滑动选择实现_第2张图片
tmp2da927f9.png

知道cell选中与不选中的规则之后,我们的任务就是找到首尾两个cell的位置,然后将之间的cell的状态改变就好。首先就是要找到第一个cell。

1、查找第一个cell

第一个cell的判断比较简单,就是看触点(x,y)坐标是否落入cell的区域内。这里需要遍历collectionView.visibleCells,因为手势滑到的地方肯定在可视范围内,因此要找的cell肯定也在visibleCells里面。只要遍历一遍,找到点的区域在cell的frame里面的cell即可。记录下cell要改变的状态firstSelectedCellChoose、cell的坐标firstChooseCellRect还有cell的位置firstChooseCellIndexPath

2、查找第二个cell

第二个cell就要根据第一个cell的位置划分成5个区域:上侧、下侧、同行左侧、同行右侧、cell中,如上图所示。前4个区域都要根据坐标判断,然后遍历collectionView.visibleCells,找到最后一个满足区域的cell就是第二个cell,如果不满足,就把model的tmpIsSelected值改回isSelected的值,达到划出区域选择恢复的效果。(这里需要注意collectionView.visibleCells并不是按上到下左到右返回的,因此还需要排个序)而在cell中这个区域不可能存在第二个cell(与第一个cell重复),因此只要把collectionView.visibleCells中所有的cell的选中状态恢复即可。具体算法可以参照demo。

自动滚动的实现

Swipe-to-Select照片滑动选择实现_第3张图片
tmp4b396b1a.png

在滑动触发事件中,我们还得认为的添加2个区域以实现自动上滑和自动下滑功能。

想要做到控制同步自动上滑和自动下滑功能,我们可以设置一个参数,scrollSpeed,当scrollSpeed > 0 代表下滑,当scrollSpeed < 0代表下滑,scrollSpeed == 0代表不滑动。这样,滑动的动画就可以用公式表示出来。

  • collectionView.ContentOffset.y = collectionView.ContentOffset.y + scrollSpeed;

这样做的好处是可以用一个变量就控制上下滑动,还能适当改变scrollSpeed的值加快或者减慢滑动速度。

  • 注意 滑动的时候不会触发滑动手势方法,必须自己调用处理方法。

相关代码:

- (void)startScroll{
    if (!startScroll ) {
        return;
    }
    if (scrollOperationQueue.operationCount > 1) {
        return ;
    }
    __weak typeof(self) wSelf = self;
    NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        if (wSelf.mainCollectionView.contentOffset.y + wSelf.mainCollectionView.frame.size.height + scrollSpeed >= wSelf.mainCollectionView.contentSize.height && scrollSpeed > 0) {
        
            [UIView animateWithDuration:0.1 animations:^{
            wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentSize.height -wSelf.mainCollectionView.frame.size.height);
            }];
        
            [wSelf stopScroll];
               return;
          }
        if (wSelf.mainCollectionView.contentOffset.y + scrollSpeed  <= 0 && scrollSpeed < 0) {
        
        [UIView animateWithDuration:0.1 animations:^{
          wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, 0);
        }];
            [wSelf stopScroll];
            return;
        
        }
    
        [UIView animateWithDuration:0.1 animations:^{
        wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentOffset.y +scrollSpeed);
    }];
        
        [wSelf dealWithPointX:scrollPoint.x pointY:scrollPoint.y];
        scrollPoint = CGPointMake(scrollPoint.x, scrollPoint.y +scrollSpeed);
        [wSelf performSelector:@selector(startScroll) withObject:nil afterDelay:0.1];
}];
    [scrollOperationQueue addOperation:operation];   
}

ps:关于滑动这块我后来又改进了下,使用UIView的动画更加流畅

总结


说了那么多,其实有很多东西只有自己去尝试后才知道是什么意思,用文字很难表达出来。

由于这个demo也是我第一次尝试,如果有什么更好方式或者效率更高的改进,欢迎在评论区提出来~

我是翻滚的牛宝宝,欢迎大家评论交流~

你可能感兴趣的:(Swipe-to-Select照片滑动选择实现)